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