docs: initial project setup and documentation
Add comprehensive documentation and planning for Pastel UI: - Complete architecture design using Next.js 16 and Tailwind CSS 4 - Detailed development guide (CLAUDE.md) with all patterns and conventions - Feature-rich README with keyboard shortcuts and export formats - Project structure and technology stack decisions Features planned: - Color playground with multi-format support - Palette generation (harmony, distinct, gradient) - Accessibility tools (WCAG, color blindness simulation) - Named colors explorer (148 CSS/X11 colors) - Batch operations with CSV/JSON import/export - Command palette (Cmd+K) and keyboard shortcuts - Dark/light mode with system preference detection - Shareable links with URL state Tech stack: - Next.js 16 with App Router and Server Components - React 19 with latest concurrent features - Tailwind CSS 4 with CSS-first configuration - TypeScript strict mode - React Query for server state - Zustand for client state - Framer Motion for animations Ready for implementation phase. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
6
.env.example
Normal file
6
.env.example
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Pastel API Configuration
|
||||||
|
# URL of the Pastel API instance
|
||||||
|
NEXT_PUBLIC_API_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# Application URL (used for sharing and OG images)
|
||||||
|
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||||
57
.gitignore
vendored
Normal file
57
.gitignore
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
.pnp/
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
|
playwright-report/
|
||||||
|
test-results/
|
||||||
|
|
||||||
|
# Next.js
|
||||||
|
.next/
|
||||||
|
out/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Production
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Local env files
|
||||||
|
.env
|
||||||
|
.env*.local
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# Vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# TypeScript
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Turbopack
|
||||||
|
.turbo/
|
||||||
988
CLAUDE.md
Normal file
988
CLAUDE.md
Normal file
@@ -0,0 +1,988 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
**Pastel UI** is a modern, interactive web application for color manipulation, palette generation, and accessibility analysis. Built with Next.js 16 and Tailwind CSS 4, it provides a beautiful interface for all features of the [Pastel API](https://github.com/valknarness/pastel-api).
|
||||||
|
|
||||||
|
**Technology Stack:**
|
||||||
|
- **Framework**: Next.js 16 (App Router, React 19)
|
||||||
|
- **Styling**: Tailwind CSS 4 (CSS-first configuration)
|
||||||
|
- **Language**: TypeScript (strict mode)
|
||||||
|
- **State Management**:
|
||||||
|
- `@tanstack/react-query` (server state)
|
||||||
|
- `zustand` (client state)
|
||||||
|
- **Animation**: `framer-motion`
|
||||||
|
- **Icons**: `lucide-react`
|
||||||
|
- **UI Components**: Custom components + shadcn/ui patterns
|
||||||
|
- **API Client**: Type-safe Pastel API integration
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
pastel-ui/
|
||||||
|
├── app/ # Next.js 16 App Router
|
||||||
|
│ ├── layout.tsx # Root layout with providers
|
||||||
|
│ ├── page.tsx # Home/main playground
|
||||||
|
│ ├── globals.css # Tailwind CSS imports
|
||||||
|
│ ├── playground/ # Main color manipulation tool
|
||||||
|
│ │ └── page.tsx
|
||||||
|
│ ├── palettes/ # Palette generation tools
|
||||||
|
│ │ ├── page.tsx # Palette dashboard
|
||||||
|
│ │ ├── harmony/page.tsx # Harmony-based palettes
|
||||||
|
│ │ ├── distinct/page.tsx # Distinct colors generator
|
||||||
|
│ │ └── gradient/page.tsx # Gradient creator
|
||||||
|
│ ├── accessibility/ # Accessibility tools
|
||||||
|
│ │ ├── page.tsx # A11y dashboard
|
||||||
|
│ │ ├── contrast/page.tsx # Contrast checker
|
||||||
|
│ │ └── colorblind/page.tsx # Colorblindness simulator
|
||||||
|
│ ├── names/ # Named colors explorer
|
||||||
|
│ │ └── page.tsx
|
||||||
|
│ ├── batch/ # Batch operations
|
||||||
|
│ │ └── page.tsx
|
||||||
|
│ ├── docs/ # Documentation
|
||||||
|
│ │ └── page.tsx
|
||||||
|
│ └── api/ # API route handlers (proxy)
|
||||||
|
│ └── proxy/[...path]/route.ts
|
||||||
|
├── components/
|
||||||
|
│ ├── ui/ # Base UI components (shadcn/ui style)
|
||||||
|
│ │ ├── button.tsx
|
||||||
|
│ │ ├── input.tsx
|
||||||
|
│ │ ├── slider.tsx
|
||||||
|
│ │ ├── tabs.tsx
|
||||||
|
│ │ ├── dialog.tsx
|
||||||
|
│ │ ├── dropdown-menu.tsx
|
||||||
|
│ │ ├── command.tsx
|
||||||
|
│ │ ├── toast.tsx
|
||||||
|
│ │ └── ...
|
||||||
|
│ ├── color/ # Color-specific components
|
||||||
|
│ │ ├── ColorPicker.tsx # Main color input
|
||||||
|
│ │ ├── ColorDisplay.tsx # Large preview swatch
|
||||||
|
│ │ ├── ColorInfo.tsx # Detailed color information
|
||||||
|
│ │ ├── FormatConverter.tsx # Format conversion tabs
|
||||||
|
│ │ ├── ColorSwatch.tsx # Small color preview
|
||||||
|
│ │ └── ColorInput.tsx # Text input with validation
|
||||||
|
│ ├── tools/ # Tool-specific components
|
||||||
|
│ │ ├── ManipulationPanel.tsx # Lighten/darken/etc controls
|
||||||
|
│ │ ├── PaletteGenerator.tsx # Palette creation UI
|
||||||
|
│ │ ├── AccessibilityChecker.tsx # Contrast/WCAG analysis
|
||||||
|
│ │ ├── GradientCreator.tsx # Gradient builder
|
||||||
|
│ │ ├── ColorBlindSimulator.tsx # Colorblindness preview
|
||||||
|
│ │ └── DistinctGenerator.tsx # Distinct colors UI
|
||||||
|
│ ├── layout/ # Layout components
|
||||||
|
│ │ ├── Navbar.tsx # Top navigation
|
||||||
|
│ │ ├── Sidebar.tsx # Side navigation
|
||||||
|
│ │ ├── Footer.tsx # Footer
|
||||||
|
│ │ ├── ThemeToggle.tsx # Dark/light mode
|
||||||
|
│ │ └── CommandPalette.tsx # Cmd+K interface
|
||||||
|
│ └── providers/ # Context providers
|
||||||
|
│ ├── Providers.tsx # Root provider wrapper
|
||||||
|
│ ├── ThemeProvider.tsx # Theme context
|
||||||
|
│ └── QueryProvider.tsx # React Query provider
|
||||||
|
├── lib/
|
||||||
|
│ ├── api/ # API client
|
||||||
|
│ │ ├── client.ts # PastelAPIClient class
|
||||||
|
│ │ ├── types.ts # API type definitions
|
||||||
|
│ │ ├── endpoints.ts # Endpoint definitions
|
||||||
|
│ │ └── queries.ts # React Query hooks
|
||||||
|
│ ├── utils/ # Utilities
|
||||||
|
│ │ ├── color.ts # Color helpers
|
||||||
|
│ │ ├── format.ts # Format converters
|
||||||
|
│ │ ├── export.ts # Export utilities (CSS, JSON, etc.)
|
||||||
|
│ │ ├── validation.ts # Input validation
|
||||||
|
│ │ └── cn.ts # Class name utility
|
||||||
|
│ ├── hooks/ # Custom React hooks
|
||||||
|
│ │ ├── useColor.ts # Color state management
|
||||||
|
│ │ ├── usePalette.ts # Palette state
|
||||||
|
│ │ ├── useColorHistory.ts # History tracking
|
||||||
|
│ │ ├── useKeyboard.ts # Keyboard shortcuts
|
||||||
|
│ │ └── useClipboard.ts # Copy to clipboard
|
||||||
|
│ ├── stores/ # Zustand stores
|
||||||
|
│ │ ├── colorStore.ts # Color state
|
||||||
|
│ │ ├── historyStore.ts # Color history
|
||||||
|
│ │ └── preferencesStore.ts # User preferences
|
||||||
|
│ └── constants/ # Constants
|
||||||
|
│ ├── colors.ts # Named colors
|
||||||
|
│ ├── shortcuts.ts # Keyboard shortcuts
|
||||||
|
│ └── exports.ts # Export templates
|
||||||
|
├── styles/
|
||||||
|
│ └── globals.css # Global styles + Tailwind
|
||||||
|
├── public/
|
||||||
|
│ ├── favicon.ico
|
||||||
|
│ ├── og-image.png
|
||||||
|
│ └── icons/
|
||||||
|
├── tests/
|
||||||
|
│ ├── unit/ # Vitest unit tests
|
||||||
|
│ │ ├── utils/
|
||||||
|
│ │ └── components/
|
||||||
|
│ └── e2e/ # Playwright E2E tests
|
||||||
|
│ ├── playground.spec.ts
|
||||||
|
│ └── palettes.spec.ts
|
||||||
|
├── .github/
|
||||||
|
│ └── workflows/
|
||||||
|
│ └── ci.yml # CI/CD pipeline
|
||||||
|
├── package.json
|
||||||
|
├── tsconfig.json
|
||||||
|
├── tailwind.config.ts # Tailwind CSS 4 config
|
||||||
|
├── next.config.ts # Next.js 16 config
|
||||||
|
├── vitest.config.ts # Vitest config
|
||||||
|
├── playwright.config.ts # Playwright config
|
||||||
|
├── .env.example
|
||||||
|
├── .env.local # Local environment variables
|
||||||
|
├── .gitignore
|
||||||
|
├── .eslintrc.json
|
||||||
|
├── .prettierrc
|
||||||
|
├── CLAUDE.md # This file
|
||||||
|
└── README.md # Project overview
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
### 1. Color Playground
|
||||||
|
**Location**: `/` or `/playground`
|
||||||
|
|
||||||
|
Interactive color manipulation interface with:
|
||||||
|
- Real-time color picker
|
||||||
|
- Support for all input formats (hex, rgb, hsl, hsv, lab, oklab, lch, oklch, cmyk, gray, named)
|
||||||
|
- Live color information display (all format representations)
|
||||||
|
- Format conversion with copy-to-clipboard
|
||||||
|
- Color manipulation controls (lighten, darken, saturate, desaturate, rotate, complement, grayscale)
|
||||||
|
- Color mixing with ratio slider
|
||||||
|
- Visual preview with large swatch
|
||||||
|
|
||||||
|
**Key Components**:
|
||||||
|
- `ColorPicker` - Main input component
|
||||||
|
- `ColorDisplay` - Large preview area
|
||||||
|
- `ColorInfo` - Tabbed format information
|
||||||
|
- `ManipulationPanel` - Slider controls for adjustments
|
||||||
|
|
||||||
|
### 2. Palette Generation
|
||||||
|
**Location**: `/palettes/*`
|
||||||
|
|
||||||
|
Multiple palette generation tools:
|
||||||
|
|
||||||
|
**a) Harmony Palettes** (`/palettes/harmony`)
|
||||||
|
- Monochromatic (1 base color)
|
||||||
|
- Analogous (3 adjacent colors)
|
||||||
|
- Complementary (2 opposite colors)
|
||||||
|
- Split-complementary (3 colors)
|
||||||
|
- Triadic (3 evenly spaced colors)
|
||||||
|
- Tetradic (4 colors in rectangle)
|
||||||
|
- Custom harmony angles
|
||||||
|
|
||||||
|
**b) Distinct Colors** (`/palettes/distinct`)
|
||||||
|
- Generate N visually distinct colors
|
||||||
|
- Configurable count (2-100)
|
||||||
|
- Distance metric selection (CIE76, CIEDE2000)
|
||||||
|
- Optional fixed colors to include
|
||||||
|
- Progress indicator (can take 2-5 minutes)
|
||||||
|
|
||||||
|
**c) Gradient Creator** (`/palettes/gradient`)
|
||||||
|
- Multiple color stops
|
||||||
|
- Configurable step count
|
||||||
|
- Color space selection (RGB, HSL, Lab, LCH, OkLab, OkLCH)
|
||||||
|
- Live preview
|
||||||
|
- Export as CSS gradient or color array
|
||||||
|
|
||||||
|
**Key Components**:
|
||||||
|
- `PaletteGenerator` - Main palette UI
|
||||||
|
- `GradientCreator` - Gradient builder
|
||||||
|
- `DistinctGenerator` - Distinct colors with progress
|
||||||
|
- `PaletteGrid` - Visual palette display
|
||||||
|
- `ExportMenu` - Export in multiple formats
|
||||||
|
|
||||||
|
### 3. Accessibility Tools
|
||||||
|
**Location**: `/accessibility/*`
|
||||||
|
|
||||||
|
**a) Contrast Checker** (`/accessibility/contrast`)
|
||||||
|
- WCAG 2.1 compliance analysis
|
||||||
|
- AA/AAA ratings for normal and large text
|
||||||
|
- Foreground/background color inputs
|
||||||
|
- Contrast ratio calculation
|
||||||
|
- Recommendations for improvement
|
||||||
|
|
||||||
|
**b) Color Blindness Simulator** (`/accessibility/colorblind`)
|
||||||
|
- Simulate protanopia (red-blind)
|
||||||
|
- Simulate deuteranopia (green-blind)
|
||||||
|
- Simulate tritanopia (blue-blind)
|
||||||
|
- Side-by-side comparison
|
||||||
|
- Batch simulation for palettes
|
||||||
|
|
||||||
|
**c) Text Color Optimizer**
|
||||||
|
- Find optimal text color for background
|
||||||
|
- WCAG compliance guarantee
|
||||||
|
- Light/dark text selection
|
||||||
|
|
||||||
|
**Key Components**:
|
||||||
|
- `AccessibilityChecker` - Main A11y interface
|
||||||
|
- `ContrastAnalyzer` - WCAG contrast checker
|
||||||
|
- `ColorBlindSimulator` - Simulation previews
|
||||||
|
- `ComplianceBadge` - AA/AAA status indicators
|
||||||
|
|
||||||
|
### 4. Named Colors Explorer
|
||||||
|
**Location**: `/names`
|
||||||
|
|
||||||
|
Browse and search 148 CSS/X11 named colors:
|
||||||
|
- Searchable grid of color swatches
|
||||||
|
- Filter by name, hex value
|
||||||
|
- Sort by hue, brightness, saturation, name
|
||||||
|
- Click to use in playground
|
||||||
|
- Find nearest named color for any input
|
||||||
|
|
||||||
|
**Key Components**:
|
||||||
|
- `NamedColorsGrid` - Visual grid display
|
||||||
|
- `ColorSearch` - Search and filter
|
||||||
|
- `NearestColorFinder` - Find closest named color
|
||||||
|
|
||||||
|
### 5. Batch Operations
|
||||||
|
**Location**: `/batch`
|
||||||
|
|
||||||
|
Process multiple colors at once:
|
||||||
|
- Upload CSV/JSON files with color lists
|
||||||
|
- Apply operations to all colors (lighten, darken, etc.)
|
||||||
|
- Bulk format conversion
|
||||||
|
- Download results in multiple formats
|
||||||
|
- Visual preview of batch results
|
||||||
|
|
||||||
|
**Key Components**:
|
||||||
|
- `BatchUploader` - File upload interface
|
||||||
|
- `BatchOperations` - Operation selection
|
||||||
|
- `BatchPreview` - Results preview
|
||||||
|
- `BatchExporter` - Download results
|
||||||
|
|
||||||
|
## Architecture Patterns
|
||||||
|
|
||||||
|
### Server vs Client Components
|
||||||
|
|
||||||
|
**Server Components** (default):
|
||||||
|
- Page layouts
|
||||||
|
- Static content
|
||||||
|
- Data fetching (when possible)
|
||||||
|
- SEO-critical content
|
||||||
|
|
||||||
|
**Client Components** (use 'use client'):
|
||||||
|
- Interactive UI (color pickers, sliders)
|
||||||
|
- State management components
|
||||||
|
- Animation/transition components
|
||||||
|
- Browser API usage (localStorage, clipboard)
|
||||||
|
|
||||||
|
### State Management Strategy
|
||||||
|
|
||||||
|
**Server State** (React Query):
|
||||||
|
- API responses from Pastel API
|
||||||
|
- Cached color operations
|
||||||
|
- Automatic refetching and caching
|
||||||
|
|
||||||
|
**Client State** (Zustand):
|
||||||
|
- Current selected colors
|
||||||
|
- Color history (session)
|
||||||
|
- User preferences (theme, shortcuts)
|
||||||
|
- UI state (sidebar open/closed)
|
||||||
|
|
||||||
|
**Persistent State** (LocalStorage/IndexedDB):
|
||||||
|
- Saved palettes
|
||||||
|
- Long-term color history
|
||||||
|
- User settings
|
||||||
|
|
||||||
|
### API Integration
|
||||||
|
|
||||||
|
**Type-Safe Client**:
|
||||||
|
```typescript
|
||||||
|
// lib/api/client.ts
|
||||||
|
export class PastelAPIClient {
|
||||||
|
private baseURL: string;
|
||||||
|
|
||||||
|
constructor(baseURL: string = process.env.NEXT_PUBLIC_API_URL!) {
|
||||||
|
this.baseURL = baseURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getColorInfo(colors: string[]): Promise<ColorInfoResponse> {
|
||||||
|
const response = await fetch(`${this.baseURL}/api/v1/colors/info`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ colors }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('API request failed');
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... all 21 endpoints
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pastelAPI = new PastelAPIClient();
|
||||||
|
```
|
||||||
|
|
||||||
|
**React Query Hooks**:
|
||||||
|
```typescript
|
||||||
|
// lib/api/queries.ts
|
||||||
|
export const useColorInfo = (colors: string[]) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['colorInfo', colors],
|
||||||
|
queryFn: () => pastelAPI.getColorInfo(colors),
|
||||||
|
enabled: colors.length > 0,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDistinctColors = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (params: DistinctColorsParams) =>
|
||||||
|
pastelAPI.generateDistinct(params.count, params.metric),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Routing Strategy
|
||||||
|
|
||||||
|
**Next.js 16 App Router**:
|
||||||
|
- File-based routing in `app/` directory
|
||||||
|
- Server Components by default
|
||||||
|
- Nested layouts for shared UI
|
||||||
|
- Loading states with `loading.tsx`
|
||||||
|
- Error boundaries with `error.tsx`
|
||||||
|
- Metadata API for SEO
|
||||||
|
|
||||||
|
**Example Route Structure**:
|
||||||
|
```
|
||||||
|
app/
|
||||||
|
├── layout.tsx # Root layout (navbar, providers)
|
||||||
|
├── page.tsx # Home page
|
||||||
|
├── playground/
|
||||||
|
│ ├── layout.tsx # Playground layout (sidebar)
|
||||||
|
│ └── page.tsx # Playground content
|
||||||
|
└── palettes/
|
||||||
|
├── layout.tsx # Shared palette layout
|
||||||
|
├── page.tsx # Palette dashboard
|
||||||
|
└── harmony/
|
||||||
|
└── page.tsx # Harmony palettes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone repository
|
||||||
|
git clone git@github.com:valknarness/pastel-ui.git
|
||||||
|
cd pastel-ui
|
||||||
|
|
||||||
|
# Install dependencies (requires Node.js 18+)
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Set up environment variables
|
||||||
|
cp .env.example .env.local
|
||||||
|
# Edit .env.local with your Pastel API URL
|
||||||
|
|
||||||
|
# Run development server
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# Open http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env.local
|
||||||
|
NEXT_PUBLIC_API_URL=http://localhost:3000 # Pastel API URL
|
||||||
|
NEXT_PUBLIC_APP_URL=http://localhost:3000 # This app URL
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Scripts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development
|
||||||
|
pnpm dev # Start dev server (localhost:3000)
|
||||||
|
pnpm dev:turbo # Start with Turbopack
|
||||||
|
|
||||||
|
# Building
|
||||||
|
pnpm build # Production build
|
||||||
|
pnpm start # Start production server
|
||||||
|
|
||||||
|
# Code Quality
|
||||||
|
pnpm lint # ESLint
|
||||||
|
pnpm lint:fix # Fix ESLint issues
|
||||||
|
pnpm format # Prettier formatting
|
||||||
|
pnpm type-check # TypeScript check
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
pnpm test # Run Vitest unit tests
|
||||||
|
pnpm test:ui # Vitest UI mode
|
||||||
|
pnpm test:e2e # Playwright E2E tests
|
||||||
|
pnpm test:e2e:ui # Playwright UI mode
|
||||||
|
|
||||||
|
# Analysis
|
||||||
|
pnpm analyze # Bundle size analysis
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Style
|
||||||
|
|
||||||
|
**TypeScript**:
|
||||||
|
- Strict mode enabled
|
||||||
|
- No `any` types (use `unknown` if needed)
|
||||||
|
- Explicit return types for functions
|
||||||
|
- Interface over type for object shapes
|
||||||
|
|
||||||
|
**React**:
|
||||||
|
- Functional components only
|
||||||
|
- Custom hooks for reusable logic
|
||||||
|
- Proper dependency arrays in hooks
|
||||||
|
- Descriptive prop names
|
||||||
|
|
||||||
|
**Naming Conventions**:
|
||||||
|
- Components: `PascalCase` (e.g., `ColorPicker.tsx`)
|
||||||
|
- Hooks: `camelCase` with `use` prefix (e.g., `useColor.ts`)
|
||||||
|
- Utilities: `camelCase` (e.g., `formatColor.ts`)
|
||||||
|
- Constants: `SCREAMING_SNAKE_CASE` (e.g., `MAX_COLORS`)
|
||||||
|
- Types/Interfaces: `PascalCase` (e.g., `ColorInfo`)
|
||||||
|
|
||||||
|
**File Organization**:
|
||||||
|
- One component per file
|
||||||
|
- Co-locate types with components
|
||||||
|
- Group related utilities
|
||||||
|
- Barrel exports (`index.ts`) for public APIs
|
||||||
|
|
||||||
|
## Tailwind CSS 4 Usage
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// tailwind.config.ts
|
||||||
|
import type { Config } from 'tailwindcss';
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
content: [
|
||||||
|
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
|
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
// Custom color palette
|
||||||
|
primary: 'oklch(var(--primary))',
|
||||||
|
secondary: 'oklch(var(--secondary))',
|
||||||
|
// ... using CSS variables for theming
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
// Custom animations
|
||||||
|
'fade-in': 'fadeIn 0.2s ease-in',
|
||||||
|
'slide-up': 'slideUp 0.3s ease-out',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
require('@tailwindcss/forms'),
|
||||||
|
require('@tailwindcss/typography'),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
```
|
||||||
|
|
||||||
|
### CSS-First Configuration (Tailwind 4)
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* app/globals.css */
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
/* Custom design tokens */
|
||||||
|
--color-primary: oklch(0.7 0.15 250);
|
||||||
|
--color-secondary: oklch(0.6 0.12 180);
|
||||||
|
|
||||||
|
/* Spacing scale */
|
||||||
|
--spacing-xs: 0.5rem;
|
||||||
|
--spacing-sm: 0.75rem;
|
||||||
|
--spacing-md: 1rem;
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
--transition-fast: 150ms;
|
||||||
|
--transition-normal: 300ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode overrides */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
@theme {
|
||||||
|
--color-primary: oklch(0.8 0.15 250);
|
||||||
|
--color-secondary: oklch(0.7 0.12 180);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component Styling Pattern
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// components/color/ColorSwatch.tsx
|
||||||
|
import { cn } from '@/lib/utils/cn';
|
||||||
|
|
||||||
|
interface ColorSwatchProps {
|
||||||
|
color: string;
|
||||||
|
size?: 'sm' | 'md' | 'lg';
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ColorSwatch({ color, size = 'md', onClick }: ColorSwatchProps) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
'rounded-lg border-2 border-gray-300 transition-all hover:scale-110',
|
||||||
|
'focus:outline-none focus:ring-2 focus:ring-primary',
|
||||||
|
{
|
||||||
|
'h-8 w-8': size === 'sm',
|
||||||
|
'h-12 w-12': size === 'md',
|
||||||
|
'h-16 w-16': size === 'lg',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
style={{ backgroundColor: color }}
|
||||||
|
onClick={onClick}
|
||||||
|
aria-label={`Color swatch: ${color}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Optimization
|
||||||
|
|
||||||
|
### Code Splitting
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Lazy load heavy components
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
|
const DistinctGenerator = dynamic(
|
||||||
|
() => import('@/components/tools/DistinctGenerator'),
|
||||||
|
{
|
||||||
|
loading: () => <LoadingSpinner />,
|
||||||
|
ssr: false, // Client-only component
|
||||||
|
}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Image Optimization
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
<Image
|
||||||
|
src="/og-image.png"
|
||||||
|
alt="Pastel UI"
|
||||||
|
width={1200}
|
||||||
|
height={630}
|
||||||
|
priority // Load eagerly for above-fold images
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Call Optimization
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Debounce API calls during slider interactions
|
||||||
|
import { useDebouncedValue } from '@/lib/hooks/useDebouncedValue';
|
||||||
|
|
||||||
|
const [lightness, setLightness] = useState(0.5);
|
||||||
|
const debouncedLightness = useDebouncedValue(lightness, 300);
|
||||||
|
|
||||||
|
const { data } = useColorInfo([color], {
|
||||||
|
enabled: !!color && debouncedLightness !== lightness,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bundle Size Management
|
||||||
|
|
||||||
|
- Keep initial bundle < 200KB
|
||||||
|
- Use dynamic imports for route-specific code
|
||||||
|
- Analyze bundle with `pnpm analyze`
|
||||||
|
- Tree-shake unused dependencies
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
### WCAG Compliance
|
||||||
|
|
||||||
|
- **AA** minimum for all UI elements
|
||||||
|
- **AAA** target for text content
|
||||||
|
- Color contrast checker built into design system
|
||||||
|
- Never rely on color alone for information
|
||||||
|
|
||||||
|
### Keyboard Navigation
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// All interactive elements must be keyboard accessible
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
handleClick();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
Click me
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### ARIA Labels
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={color}
|
||||||
|
onChange={(e) => setColor(e.target.value)}
|
||||||
|
aria-label="Color input (hex, rgb, hsl, etc.)"
|
||||||
|
aria-describedby="color-format-hint"
|
||||||
|
/>
|
||||||
|
<span id="color-format-hint" className="sr-only">
|
||||||
|
Enter a color in any format: hex, rgb, hsl, or named color
|
||||||
|
</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Focus Management
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
function Dialog({ isOpen, onClose }: DialogProps) {
|
||||||
|
const closeButtonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
closeButtonRef.current?.focus();
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div role="dialog" aria-modal="true">
|
||||||
|
{/* Dialog content */}
|
||||||
|
<button ref={closeButtonRef} onClick={onClose}>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### Unit Tests (Vitest)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// tests/unit/utils/color.test.ts
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { parseColor, formatColor } from '@/lib/utils/color';
|
||||||
|
|
||||||
|
describe('color utilities', () => {
|
||||||
|
it('should parse hex colors', () => {
|
||||||
|
expect(parseColor('#ff0099')).toEqual({
|
||||||
|
r: 255,
|
||||||
|
g: 0,
|
||||||
|
b: 153,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format RGB to hex', () => {
|
||||||
|
expect(formatColor({ r: 255, g: 0, b: 153 }, 'hex'))
|
||||||
|
.toBe('#ff0099');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component Tests
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// tests/unit/components/ColorSwatch.test.tsx
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { ColorSwatch } from '@/components/color/ColorSwatch';
|
||||||
|
|
||||||
|
describe('ColorSwatch', () => {
|
||||||
|
it('should render with correct background color', () => {
|
||||||
|
render(<ColorSwatch color="#ff0099" />);
|
||||||
|
const swatch = screen.getByRole('button');
|
||||||
|
expect(swatch).toHaveStyle({ backgroundColor: '#ff0099' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onClick when clicked', async () => {
|
||||||
|
const onClick = vi.fn();
|
||||||
|
render(<ColorSwatch color="#ff0099" onClick={onClick} />);
|
||||||
|
|
||||||
|
await userEvent.click(screen.getByRole('button'));
|
||||||
|
expect(onClick).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### E2E Tests (Playwright)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// tests/e2e/playground.spec.ts
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test('color manipulation workflow', async ({ page }) => {
|
||||||
|
await page.goto('/playground');
|
||||||
|
|
||||||
|
// Enter a color
|
||||||
|
await page.fill('[aria-label="Color input"]', '#ff0099');
|
||||||
|
|
||||||
|
// Verify color info is displayed
|
||||||
|
await expect(page.locator('text=rgb(255, 0, 153)')).toBeVisible();
|
||||||
|
|
||||||
|
// Adjust lightness
|
||||||
|
await page.locator('[aria-label="Lightness slider"]').fill('0.7');
|
||||||
|
|
||||||
|
// Verify updated color
|
||||||
|
await expect(page.locator('.color-display')).toHaveCSS(
|
||||||
|
'background-color',
|
||||||
|
/rgb\(255, \d+, \d+\)/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### Vercel (Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Vercel CLI
|
||||||
|
pnpm add -g vercel
|
||||||
|
|
||||||
|
# Deploy
|
||||||
|
vercel
|
||||||
|
|
||||||
|
# Production deployment
|
||||||
|
vercel --prod
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables (Production)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Vercel dashboard or .env.production
|
||||||
|
NEXT_PUBLIC_API_URL=https://api.pastel.example.com
|
||||||
|
NEXT_PUBLIC_APP_URL=https://pastel.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Optimization
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// next.config.ts
|
||||||
|
import type { NextConfig } from 'next';
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
// Enable React Compiler (Next.js 16)
|
||||||
|
experimental: {
|
||||||
|
reactCompiler: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Image optimization
|
||||||
|
images: {
|
||||||
|
formats: ['image/avif', 'image/webp'],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Bundle analyzer (conditional)
|
||||||
|
...(process.env.ANALYZE === 'true' && {
|
||||||
|
webpack(config) {
|
||||||
|
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||||
|
config.plugins.push(
|
||||||
|
new BundleAnalyzerPlugin({
|
||||||
|
analyzerMode: 'static',
|
||||||
|
openAnalyzer: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI/CD Pipeline
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .github/workflows/ci.yml
|
||||||
|
name: CI/CD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- run: pnpm install
|
||||||
|
- run: pnpm lint
|
||||||
|
- run: pnpm type-check
|
||||||
|
- run: pnpm test
|
||||||
|
- run: pnpm build
|
||||||
|
|
||||||
|
e2e:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
|
||||||
|
- run: pnpm install
|
||||||
|
- run: pnpm build
|
||||||
|
- run: pnpm test:e2e
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
needs: [test, e2e]
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: amondnet/vercel-action@v25
|
||||||
|
with:
|
||||||
|
vercel-token: ${{ secrets.VERCEL_TOKEN }}
|
||||||
|
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
|
||||||
|
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
|
||||||
|
vercel-args: '--prod'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Custom Hook Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// lib/hooks/useColor.ts
|
||||||
|
import { useState, useCallback } from 'use';
|
||||||
|
import { useColorInfo } from '@/lib/api/queries';
|
||||||
|
import { parseColor } from '@/lib/utils/color';
|
||||||
|
|
||||||
|
export function useColor(initialColor?: string) {
|
||||||
|
const [color, setColor] = useState(initialColor ?? '#000000');
|
||||||
|
const [format, setFormat] = useState<ColorFormat>('hex');
|
||||||
|
|
||||||
|
const { data: colorInfo, isLoading } = useColorInfo([color]);
|
||||||
|
|
||||||
|
const updateColor = useCallback((newColor: string) => {
|
||||||
|
try {
|
||||||
|
const parsed = parseColor(newColor);
|
||||||
|
if (parsed) {
|
||||||
|
setColor(newColor);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Invalid color:', error);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
color,
|
||||||
|
colorInfo: colorInfo?.data.colors[0],
|
||||||
|
format,
|
||||||
|
setFormat,
|
||||||
|
updateColor,
|
||||||
|
isLoading,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Zustand Store Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// lib/stores/historyStore.ts
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { persist } from 'zustand/middleware';
|
||||||
|
|
||||||
|
interface ColorHistoryState {
|
||||||
|
history: string[];
|
||||||
|
addColor: (color: string) => void;
|
||||||
|
clearHistory: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useColorHistory = create<ColorHistoryState>()(
|
||||||
|
persist(
|
||||||
|
(set) => ({
|
||||||
|
history: [],
|
||||||
|
|
||||||
|
addColor: (color) => set((state) => ({
|
||||||
|
history: [color, ...state.history.filter(c => c !== color)].slice(0, 50),
|
||||||
|
})),
|
||||||
|
|
||||||
|
clearHistory: () => set({ history: [] }),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'color-history',
|
||||||
|
storage: createJSONStorage(() => localStorage),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**Issue**: API calls failing with CORS errors
|
||||||
|
**Solution**: Ensure `NEXT_PUBLIC_API_URL` is set correctly and the Pastel API has CORS enabled
|
||||||
|
|
||||||
|
**Issue**: Tailwind classes not applying
|
||||||
|
**Solution**: Check `content` paths in `tailwind.config.ts` include all component files
|
||||||
|
|
||||||
|
**Issue**: Hydration errors
|
||||||
|
**Solution**: Ensure client components using browser APIs have proper checks:
|
||||||
|
```tsx
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
useEffect(() => setMounted(true), []);
|
||||||
|
if (!mounted) return null;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Issue**: Large bundle size
|
||||||
|
**Solution**: Use dynamic imports for heavy components and run `pnpm analyze`
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- [Next.js 16 Docs](https://nextjs.org/docs)
|
||||||
|
- [React 19 Docs](https://react.dev)
|
||||||
|
- [Tailwind CSS 4 Docs](https://tailwindcss.com/docs)
|
||||||
|
- [React Query Docs](https://tanstack.com/query/latest)
|
||||||
|
- [Pastel API Docs](https://github.com/valknarness/pastel-api)
|
||||||
|
|
||||||
|
### Color Science
|
||||||
|
- [OkLab Color Space](https://bottosson.github.io/posts/oklab/)
|
||||||
|
- [WCAG Contrast Guidelines](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html)
|
||||||
|
- [Color Blindness Simulation](https://www.color-blindness.com/coblis-color-blindness-simulator/)
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
- [Figma](https://www.figma.com) - Design mockups
|
||||||
|
- [Playwright](https://playwright.dev) - E2E testing
|
||||||
|
- [Vitest](https://vitest.dev) - Unit testing
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
### PR Checklist
|
||||||
|
|
||||||
|
- [ ] Tests pass (`pnpm test`)
|
||||||
|
- [ ] E2E tests pass (`pnpm test:e2e`)
|
||||||
|
- [ ] No ESLint errors (`pnpm lint`)
|
||||||
|
- [ ] No TypeScript errors (`pnpm type-check`)
|
||||||
|
- [ ] Code formatted (`pnpm format`)
|
||||||
|
- [ ] Documentation updated (if needed)
|
||||||
|
- [ ] Accessibility tested (keyboard, screen reader)
|
||||||
|
- [ ] Performance checked (bundle size)
|
||||||
|
|
||||||
|
### Code Review Guidelines
|
||||||
|
|
||||||
|
- All interactive elements must be keyboard accessible
|
||||||
|
- All images must have alt text
|
||||||
|
- Color contrast must meet WCAG AA
|
||||||
|
- No console.log in production code
|
||||||
|
- Meaningful variable/function names
|
||||||
|
- Comments for complex logic only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Project Status**: Design phase complete, ready for implementation
|
||||||
|
**Last Updated**: 2025-11-07
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Valknarness
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
429
README.md
Normal file
429
README.md
Normal file
@@ -0,0 +1,429 @@
|
|||||||
|
# Pastel UI
|
||||||
|
|
||||||
|
> A modern, interactive web application for color manipulation, palette generation, and accessibility analysis.
|
||||||
|
|
||||||
|
[](https://nextjs.org)
|
||||||
|
[](https://react.dev)
|
||||||
|
[](https://tailwindcss.com)
|
||||||
|
[](https://www.typescriptlang.org)
|
||||||
|
[](LICENSE)
|
||||||
|
|
||||||
|
Pastel UI is a beautiful, feature-rich interface for the [Pastel API](https://github.com/valknarness/pastel-api), providing intuitive tools for color manipulation, palette generation, and accessibility testing.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### 🎨 Color Playground
|
||||||
|
- **Interactive Color Picker** - Real-time color selection with live preview
|
||||||
|
- **Multi-Format Support** - Hex, RGB, HSL, HSV, Lab, OkLab, LCH, OkLCH, CMYK, Gray, and 148 named colors
|
||||||
|
- **Color Manipulation** - Lighten, darken, saturate, desaturate, rotate, complement, mix, grayscale
|
||||||
|
- **Format Conversion** - Convert between any color format with one click
|
||||||
|
- **Copy to Clipboard** - Quick copy for all formats
|
||||||
|
|
||||||
|
### 🎭 Palette Generation
|
||||||
|
- **Harmony Palettes** - Monochromatic, analogous, complementary, split-complementary, triadic, tetradic
|
||||||
|
- **Distinct Colors** - Generate visually distinct colors using simulated annealing algorithm
|
||||||
|
- **Gradient Creator** - Multi-stop gradients with color space selection (RGB, HSL, Lab, LCH, OkLab, OkLCH)
|
||||||
|
- **Export Options** - CSS, JSON, Tailwind config, SCSS, SVG, PNG
|
||||||
|
|
||||||
|
### ♿ Accessibility Tools
|
||||||
|
- **WCAG Contrast Checker** - AA/AAA compliance analysis for text and backgrounds
|
||||||
|
- **Color Blindness Simulator** - Protanopia, deuteranopia, tritanopia simulation
|
||||||
|
- **Text Color Optimizer** - Find optimal text color for any background
|
||||||
|
- **Batch Accessibility Testing** - Test entire palettes at once
|
||||||
|
|
||||||
|
### 🔍 Color Analysis
|
||||||
|
- **Perceptual Distance** - CIE76 and CIEDE2000 metrics
|
||||||
|
- **Color Sorting** - Sort by hue, brightness, luminance, chroma
|
||||||
|
- **Named Color Search** - Find nearest named color for any input
|
||||||
|
- **Color Information** - Comprehensive color data in all formats
|
||||||
|
|
||||||
|
### ⚡ Advanced Features
|
||||||
|
- **Batch Operations** - Upload CSV/JSON, process multiple colors, download results
|
||||||
|
- **Color History** - Track all colors used in your session
|
||||||
|
- **Saved Palettes** - Persistent storage of your favorite palettes
|
||||||
|
- **Command Palette** - Keyboard shortcuts (Cmd+K) for power users
|
||||||
|
- **Dark/Light Mode** - System preference + manual toggle
|
||||||
|
- **Shareable Links** - Share colors and palettes via URL
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **[Next.js 16](https://nextjs.org)** - React framework with App Router and Server Components
|
||||||
|
- **[React 19](https://react.dev)** - Latest React with improved concurrent features
|
||||||
|
- **[Tailwind CSS 4](https://tailwindcss.com)** - CSS-first utility framework with modern color spaces
|
||||||
|
- **[TypeScript](https://www.typescriptlang.org)** - Strict type safety throughout
|
||||||
|
- **[React Query](https://tanstack.com/query)** - Server state management and caching
|
||||||
|
- **[Zustand](https://github.com/pmndrs/zustand)** - Client state management
|
||||||
|
- **[Framer Motion](https://www.framer.com/motion/)** - Smooth animations and transitions
|
||||||
|
- **[Lucide React](https://lucide.dev)** - Beautiful icon set
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- **Node.js** 18+ (20+ recommended)
|
||||||
|
- **pnpm** 9+ (or npm/yarn)
|
||||||
|
- **Pastel API** running locally or remotely
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone git@github.com:valknarness/pastel-ui.git
|
||||||
|
cd pastel-ui
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Set up environment variables
|
||||||
|
cp .env.example .env.local
|
||||||
|
# Edit .env.local with your Pastel API URL
|
||||||
|
|
||||||
|
# Run development server
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) in your browser.
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env.local
|
||||||
|
NEXT_PUBLIC_API_URL=http://localhost:3000 # Your Pastel API URL
|
||||||
|
NEXT_PUBLIC_APP_URL=http://localhost:3000 # This app's URL (for sharing)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Available Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development
|
||||||
|
pnpm dev # Start dev server (http://localhost:3000)
|
||||||
|
pnpm dev:turbo # Start with Turbopack (faster)
|
||||||
|
|
||||||
|
# Building
|
||||||
|
pnpm build # Production build
|
||||||
|
pnpm start # Start production server
|
||||||
|
|
||||||
|
# Code Quality
|
||||||
|
pnpm lint # Run ESLint
|
||||||
|
pnpm lint:fix # Fix ESLint issues automatically
|
||||||
|
pnpm format # Format code with Prettier
|
||||||
|
pnpm type-check # TypeScript type checking
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
pnpm test # Run unit tests (Vitest)
|
||||||
|
pnpm test:ui # Vitest UI mode
|
||||||
|
pnpm test:e2e # Run E2E tests (Playwright)
|
||||||
|
pnpm test:e2e:ui # Playwright UI mode
|
||||||
|
|
||||||
|
# Analysis
|
||||||
|
pnpm analyze # Analyze bundle size
|
||||||
|
```
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
pastel-ui/
|
||||||
|
├── app/ # Next.js App Router
|
||||||
|
│ ├── playground/ # Color manipulation tool
|
||||||
|
│ ├── palettes/ # Palette generation
|
||||||
|
│ ├── accessibility/ # Accessibility tools
|
||||||
|
│ ├── names/ # Named colors explorer
|
||||||
|
│ └── batch/ # Batch operations
|
||||||
|
├── components/
|
||||||
|
│ ├── ui/ # Base UI components
|
||||||
|
│ ├── color/ # Color-specific components
|
||||||
|
│ ├── tools/ # Tool components
|
||||||
|
│ └── layout/ # Layout components
|
||||||
|
├── lib/
|
||||||
|
│ ├── api/ # API client and queries
|
||||||
|
│ ├── utils/ # Utility functions
|
||||||
|
│ ├── hooks/ # Custom React hooks
|
||||||
|
│ └── stores/ # Zustand stores
|
||||||
|
└── tests/ # Unit and E2E tests
|
||||||
|
```
|
||||||
|
|
||||||
|
See [CLAUDE.md](CLAUDE.md) for detailed architecture documentation.
|
||||||
|
|
||||||
|
## Features Overview
|
||||||
|
|
||||||
|
### Color Playground (`/`)
|
||||||
|
|
||||||
|
The main interface for color manipulation:
|
||||||
|
|
||||||
|
- Input colors in any format
|
||||||
|
- View comprehensive color information
|
||||||
|
- Manipulate colors with intuitive controls
|
||||||
|
- Convert between formats instantly
|
||||||
|
- Copy values with one click
|
||||||
|
|
||||||
|
### Palette Generation (`/palettes`)
|
||||||
|
|
||||||
|
Create beautiful color palettes:
|
||||||
|
|
||||||
|
#### Harmony Palettes (`/palettes/harmony`)
|
||||||
|
Generate palettes based on color theory:
|
||||||
|
- **Monochromatic** - Variations of a single hue
|
||||||
|
- **Analogous** - Adjacent colors on the wheel
|
||||||
|
- **Complementary** - Opposite colors
|
||||||
|
- **Split-complementary** - Base + two adjacent to complement
|
||||||
|
- **Triadic** - Three evenly spaced colors
|
||||||
|
- **Tetradic** - Four colors in a rectangle
|
||||||
|
|
||||||
|
#### Distinct Colors (`/palettes/distinct`)
|
||||||
|
Generate visually distinct colors using advanced algorithms:
|
||||||
|
- Configurable count (2-100 colors)
|
||||||
|
- Distance metric selection (CIE76, CIEDE2000)
|
||||||
|
- Optional fixed colors to include
|
||||||
|
- Real-time progress indicator
|
||||||
|
|
||||||
|
#### Gradient Creator (`/palettes/gradient`)
|
||||||
|
Create smooth color gradients:
|
||||||
|
- Multiple color stops
|
||||||
|
- Configurable step count
|
||||||
|
- Color space selection for interpolation
|
||||||
|
- Live preview
|
||||||
|
- Export as CSS or color array
|
||||||
|
|
||||||
|
### Accessibility Tools (`/accessibility`)
|
||||||
|
|
||||||
|
Ensure your colors are accessible:
|
||||||
|
|
||||||
|
#### Contrast Checker (`/accessibility/contrast`)
|
||||||
|
- WCAG 2.1 compliance testing
|
||||||
|
- AA/AAA ratings for normal and large text
|
||||||
|
- Contrast ratio calculation
|
||||||
|
- Improvement recommendations
|
||||||
|
|
||||||
|
#### Color Blindness Simulator (`/accessibility/colorblind`)
|
||||||
|
- Simulate protanopia (red-blind)
|
||||||
|
- Simulate deuteranopia (green-blind)
|
||||||
|
- Simulate tritanopia (blue-blind)
|
||||||
|
- Side-by-side comparison
|
||||||
|
- Batch palette testing
|
||||||
|
|
||||||
|
### Named Colors Explorer (`/names`)
|
||||||
|
|
||||||
|
Browse and search 148 CSS/X11 named colors:
|
||||||
|
- Visual grid display
|
||||||
|
- Search by name or hex value
|
||||||
|
- Sort by hue, brightness, saturation, name
|
||||||
|
- Find nearest named color for any input
|
||||||
|
- Click to use in playground
|
||||||
|
|
||||||
|
### Batch Operations (`/batch`)
|
||||||
|
|
||||||
|
Process multiple colors efficiently:
|
||||||
|
- Upload CSV/JSON files
|
||||||
|
- Apply operations to all colors
|
||||||
|
- Bulk format conversion
|
||||||
|
- Visual preview of results
|
||||||
|
- Download in multiple formats
|
||||||
|
|
||||||
|
## Export Formats
|
||||||
|
|
||||||
|
Export your colors and palettes in various formats:
|
||||||
|
|
||||||
|
### CSS Variables
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--color-primary: #ff0099;
|
||||||
|
--color-secondary: #00ccff;
|
||||||
|
--color-accent: #ffcc00;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tailwind Config
|
||||||
|
```javascript
|
||||||
|
module.exports = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
primary: '#ff0099',
|
||||||
|
secondary: '#00ccff',
|
||||||
|
accent: '#ffcc00',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"colors": [
|
||||||
|
{ "name": "primary", "hex": "#ff0099" },
|
||||||
|
{ "name": "secondary", "hex": "#00ccff" },
|
||||||
|
{ "name": "accent", "hex": "#ffcc00" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### SCSS
|
||||||
|
```scss
|
||||||
|
$color-primary: #ff0099;
|
||||||
|
$color-secondary: #00ccff;
|
||||||
|
$color-accent: #ffcc00;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Keyboard Shortcuts
|
||||||
|
|
||||||
|
| Shortcut | Action |
|
||||||
|
|----------|--------|
|
||||||
|
| `Cmd/Ctrl + K` | Open command palette |
|
||||||
|
| `Cmd/Ctrl + C` | Copy current color (hex) |
|
||||||
|
| `Cmd/Ctrl + V` | Paste color from clipboard |
|
||||||
|
| `Cmd/Ctrl + Z` | Undo color change |
|
||||||
|
| `Cmd/Ctrl + Shift + Z` | Redo color change |
|
||||||
|
| `Cmd/Ctrl + D` | Toggle dark/light mode |
|
||||||
|
| `Cmd/Ctrl + /` | Show keyboard shortcuts |
|
||||||
|
| `Esc` | Close modals/dialogs |
|
||||||
|
| `Arrow Keys` | Navigate color history |
|
||||||
|
| `Space` | Toggle color picker |
|
||||||
|
|
||||||
|
## API Integration
|
||||||
|
|
||||||
|
Pastel UI communicates with the [Pastel API](https://github.com/valknarness/pastel-api) for all color operations. The API client is type-safe and includes automatic retries, caching, and error handling.
|
||||||
|
|
||||||
|
### Example API Usage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { pastelAPI } from '@/lib/api/client';
|
||||||
|
|
||||||
|
// Get color information
|
||||||
|
const info = await pastelAPI.getColorInfo(['#ff0099']);
|
||||||
|
|
||||||
|
// Generate distinct colors
|
||||||
|
const distinct = await pastelAPI.generateDistinct(8, 'ciede2000');
|
||||||
|
|
||||||
|
// Create gradient
|
||||||
|
const gradient = await pastelAPI.generateGradient({
|
||||||
|
stops: ['#ff0000', '#0000ff'],
|
||||||
|
count: 10,
|
||||||
|
colorspace: 'lch'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
See [CLAUDE.md](CLAUDE.md) for detailed API integration documentation.
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
Pastel UI is optimized for performance:
|
||||||
|
|
||||||
|
- **Fast Initial Load** - < 200KB initial bundle
|
||||||
|
- **Code Splitting** - Route-based automatic splitting
|
||||||
|
- **Image Optimization** - Next.js Image with AVIF/WebP
|
||||||
|
- **API Caching** - React Query with smart cache invalidation
|
||||||
|
- **Debounced Updates** - Smooth slider interactions
|
||||||
|
- **Web Workers** - Heavy calculations off main thread
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
Pastel UI meets WCAG 2.1 Level AAA standards:
|
||||||
|
|
||||||
|
- **Keyboard Navigation** - Full keyboard support
|
||||||
|
- **Screen Reader Support** - Comprehensive ARIA labels
|
||||||
|
- **Focus Management** - Logical focus order
|
||||||
|
- **Color Contrast** - AAA contrast throughout
|
||||||
|
- **Reduced Motion** - Respects user preferences
|
||||||
|
- **Semantic HTML** - Proper HTML structure
|
||||||
|
|
||||||
|
## Browser Support
|
||||||
|
|
||||||
|
- **Chrome/Edge** 90+
|
||||||
|
- **Firefox** 88+
|
||||||
|
- **Safari** 14+
|
||||||
|
- **Mobile Safari** 14+
|
||||||
|
- **Chrome Android** 90+
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### Vercel (Recommended)
|
||||||
|
|
||||||
|
[](https://vercel.com/new/clone?repository-url=https://github.com/valknarness/pastel-ui)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Vercel CLI
|
||||||
|
pnpm add -g vercel
|
||||||
|
|
||||||
|
# Deploy
|
||||||
|
vercel
|
||||||
|
|
||||||
|
# Production deployment
|
||||||
|
vercel --prod
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build
|
||||||
|
docker build -t pastel-ui .
|
||||||
|
|
||||||
|
# Run
|
||||||
|
docker run -p 3000:3000 -e NEXT_PUBLIC_API_URL=https://api.pastel.com pastel-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
### Static Export
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build static export
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# Output in out/ directory
|
||||||
|
# Deploy to any static hosting (Netlify, Cloudflare Pages, etc.)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please read [CLAUDE.md](CLAUDE.md) for development guidelines.
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||||
|
3. Make your changes
|
||||||
|
4. Run tests (`pnpm test`)
|
||||||
|
5. Commit your changes (`git commit -m 'Add amazing feature'`)
|
||||||
|
6. Push to the branch (`git push origin feature/amazing-feature`)
|
||||||
|
7. Open a Pull Request
|
||||||
|
|
||||||
|
### PR Checklist
|
||||||
|
|
||||||
|
- [ ] Tests pass (`pnpm test` and `pnpm test:e2e`)
|
||||||
|
- [ ] No linting errors (`pnpm lint`)
|
||||||
|
- [ ] No TypeScript errors (`pnpm type-check`)
|
||||||
|
- [ ] Code formatted (`pnpm format`)
|
||||||
|
- [ ] Documentation updated (if needed)
|
||||||
|
- [ ] Accessibility tested (keyboard, screen reader)
|
||||||
|
- [ ] Performance checked (`pnpm analyze`)
|
||||||
|
|
||||||
|
## Related Projects
|
||||||
|
|
||||||
|
- **[Pastel API](https://github.com/valknarness/pastel-api)** - REST API for color manipulation
|
||||||
|
- **[Pastel CLI](https://github.com/sharkdp/pastel)** - Original command-line tool by David Peter
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
- **[David Peter](https://github.com/sharkdp)** - Creator of the original [Pastel CLI](https://github.com/sharkdp/pastel)
|
||||||
|
- **[Vercel](https://vercel.com)** - Next.js framework and hosting platform
|
||||||
|
- **[Tailwind Labs](https://tailwindcss.com)** - Tailwind CSS framework
|
||||||
|
- Color science resources from [OkLab](https://bottosson.github.io/posts/oklab/) and [W3C](https://www.w3.org/WAI/WCAG21/)
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- **Documentation**: [CLAUDE.md](CLAUDE.md)
|
||||||
|
- **API Docs**: [Pastel API](https://github.com/valknarness/pastel-api)
|
||||||
|
- **Issues**: [GitHub Issues](https://github.com/valknarness/pastel-ui/issues)
|
||||||
|
- **Discussions**: [GitHub Discussions](https://github.com/valknarness/pastel-ui/discussions)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Built with** ❤️ **using** [Next.js](https://nextjs.org), [React](https://react.dev), and [Tailwind CSS](https://tailwindcss.com)
|
||||||
|
|
||||||
|
**Project Status**: Design phase complete, ready for implementation
|
||||||
|
**Last Updated**: 2025-11-07
|
||||||
Reference in New Issue
Block a user