create-roadkit
Version:
Beautiful Next.js roadmap website generator with full-screen kanban boards, dark/light mode, and static export
391 lines (347 loc) • 13.8 kB
text/typescript
/**
* Predefined theme configurations based on shadcn/ui design principles
*
* This file contains all the built-in themes that come with roadkit,
* following shadcn/ui color schemes and accessibility guidelines.
*/
import type { Theme, ColorPalette, ThemeConfig, ColorScheme, ShadcnStyle } from './types';
/**
* Creates a consistent color palette for light mode themes
* Base colors are derived from shadcn/ui's color system
* All colors are WCAG AA compliant with minimum 4.5:1 contrast ratios
*/
function createLightPalette(baseHue: number, saturation: number): ColorPalette {
// For light mode, we need darker primary colors for sufficient contrast with white text
// Adjust lightness based on saturation to ensure WCAG AA compliance
// Special handling for problematic high-saturation colors like green and yellow
let primaryLightness: number;
if (saturation >= 90) {
primaryLightness = 28; // Much darker for very high saturation (yellow)
} else if (saturation > 60) {
primaryLightness = 30; // Darker for high saturation (green, blue)
} else {
primaryLightness = 41; // Standard lightness for lower saturation
}
return {
background: { name: 'background', value: '0 0% 100%', description: 'Default page background' },
foreground: { name: 'foreground', value: `${baseHue} 6% 10%`, description: 'Default text color' },
card: { name: 'card', value: '0 0% 100%', description: 'Card background' },
'card-foreground': { name: 'card-foreground', value: `${baseHue} 6% 10%`, description: 'Card text color' },
popover: { name: 'popover', value: '0 0% 100%', description: 'Popover background' },
'popover-foreground': { name: 'popover-foreground', value: `${baseHue} 6% 10%`, description: 'Popover text color' },
primary: { name: 'primary', value: `${baseHue} ${Math.min(saturation, 70)}% ${primaryLightness}%`, description: 'Primary brand color' },
'primary-foreground': { name: 'primary-foreground', value: '0 0% 100%', description: 'Primary button text' },
secondary: { name: 'secondary', value: `${baseHue} 4% 96%`, description: 'Secondary button background' },
'secondary-foreground': { name: 'secondary-foreground', value: `${baseHue} 6% 10%`, description: 'Secondary button text' },
muted: { name: 'muted', value: `${baseHue} 4% 96%`, description: 'Muted background' },
'muted-foreground': { name: 'muted-foreground', value: `${baseHue} 4% 38%`, description: 'Muted text' },
accent: { name: 'accent', value: `${baseHue} 4% 96%`, description: 'Accent background' },
'accent-foreground': { name: 'accent-foreground', value: `${baseHue} 6% 10%`, description: 'Accent text' },
destructive: { name: 'destructive', value: '0 72% 50%', description: 'Error/destructive color' },
'destructive-foreground': { name: 'destructive-foreground', value: '0 0% 100%', description: 'Error button text' },
border: { name: 'border', value: `${baseHue} 6% 89%`, description: 'Default border color' },
input: { name: 'input', value: `${baseHue} 6% 89%`, description: 'Input border color' },
ring: { name: 'ring', value: `${baseHue} ${Math.min(saturation, 70)}% 41%`, description: 'Focus ring color' },
// Chart colors for data visualization
'chart-1': { name: 'chart-1', value: `${baseHue} ${Math.min(saturation, 70)}% 41%`, description: 'First chart color' },
'chart-2': { name: 'chart-2', value: `${(baseHue + 60) % 360} ${Math.min(saturation, 70)}% 41%`, description: 'Second chart color' },
'chart-3': { name: 'chart-3', value: `${(baseHue + 120) % 360} ${Math.min(saturation, 70)}% 41%`, description: 'Third chart color' },
'chart-4': { name: 'chart-4', value: `${(baseHue + 180) % 360} ${Math.min(saturation, 70)}% 41%`, description: 'Fourth chart color' },
'chart-5': { name: 'chart-5', value: `${(baseHue + 240) % 360} ${Math.min(saturation, 70)}% 41%`, description: 'Fifth chart color' },
};
}
/**
* Creates a consistent color palette for dark mode themes
* Ensures proper contrast ratios while maintaining brand consistency
* All colors are WCAG AA compliant with minimum 4.5:1 contrast ratios
*/
function createDarkPalette(baseHue: number, saturation: number): ColorPalette {
// For dark mode, we need lighter primary colors for sufficient contrast with dark text
// Adjust lightness based on saturation to ensure WCAG AA compliance
const primaryLightness = saturation > 60 ? 65 : 59; // Lighter for high saturation colors
return {
background: { name: 'background', value: `${baseHue} 6% 3%`, description: 'Dark mode page background' },
foreground: { name: 'foreground', value: `${baseHue} 5% 90%`, description: 'Dark mode text color' },
card: { name: 'card', value: `${baseHue} 6% 3%`, description: 'Dark mode card background' },
'card-foreground': { name: 'card-foreground', value: `${baseHue} 5% 90%`, description: 'Dark mode card text' },
popover: { name: 'popover', value: `${baseHue} 6% 3%`, description: 'Dark mode popover background' },
'popover-foreground': { name: 'popover-foreground', value: `${baseHue} 5% 90%`, description: 'Dark mode popover text' },
primary: { name: 'primary', value: `${baseHue} ${Math.min(saturation, 70)}% ${primaryLightness}%`, description: 'Dark mode primary color' },
'primary-foreground': { name: 'primary-foreground', value: `${baseHue} 6% 10%`, description: 'Dark mode primary text' },
secondary: { name: 'secondary', value: `${baseHue} 6% 10%`, description: 'Dark mode secondary background' },
'secondary-foreground': { name: 'secondary-foreground', value: `${baseHue} 5% 90%`, description: 'Dark mode secondary text' },
muted: { name: 'muted', value: `${baseHue} 6% 10%`, description: 'Dark mode muted background' },
'muted-foreground': { name: 'muted-foreground', value: `${baseHue} 4% 70%`, description: 'Dark mode muted text' },
accent: { name: 'accent', value: `${baseHue} 6% 10%`, description: 'Dark mode accent background' },
'accent-foreground': { name: 'accent-foreground', value: `${baseHue} 5% 90%`, description: 'Dark mode accent text' },
destructive: { name: 'destructive', value: '0 63% 51%', description: 'Dark mode error color' },
'destructive-foreground': { name: 'destructive-foreground', value: '0 0% 100%', description: 'Dark mode error text' },
border: { name: 'border', value: `${baseHue} 6% 10%`, description: 'Dark mode border color' },
input: { name: 'input', value: `${baseHue} 6% 10%`, description: 'Dark mode input border' },
ring: { name: 'ring', value: `${baseHue} ${Math.min(saturation, 70)}% 59%`, description: 'Dark mode focus ring' },
// Chart colors optimized for dark mode
'chart-1': { name: 'chart-1', value: `${baseHue} ${Math.min(saturation, 70)}% 59%`, description: 'First dark chart color' },
'chart-2': { name: 'chart-2', value: `${(baseHue + 60) % 360} ${Math.min(saturation, 70)}% 59%`, description: 'Second dark chart color' },
'chart-3': { name: 'chart-3', value: `${(baseHue + 120) % 360} ${Math.min(saturation, 70)}% 59%`, description: 'Third dark chart color' },
'chart-4': { name: 'chart-4', value: `${(baseHue + 180) % 360} ${Math.min(saturation, 70)}% 59%`, description: 'Fourth dark chart color' },
'chart-5': { name: 'chart-5', value: `${(baseHue + 240) % 360} ${Math.min(saturation, 70)}% 59%`, description: 'Fifth dark chart color' },
};
}
/**
* Standard border radius values following shadcn/ui conventions
*/
const BORDER_RADIUS = {
sm: '0.375rem', // 6px
md: '0.5rem', // 8px
lg: '0.75rem', // 12px
xl: '1rem', // 16px
};
/**
* Creates a complete theme configuration with both light and dark modes
* Ensures all themes meet WCAG AA accessibility standards
*/
function createTheme(
id: string,
name: string,
colorScheme: ColorScheme,
style: ShadcnStyle,
baseHue: number,
saturation: number,
description: string
): Theme {
const lightConfig: ThemeConfig = {
id: `${id}-light`,
name: `${name} Light`,
mode: 'light',
colorScheme,
style,
palette: createLightPalette(baseHue, saturation),
borderRadius: BORDER_RADIUS,
};
const darkConfig: ThemeConfig = {
id: `${id}-dark`,
name: `${name} Dark`,
mode: 'dark',
colorScheme,
style,
palette: createDarkPalette(baseHue, saturation),
borderRadius: BORDER_RADIUS,
};
return {
id,
name,
colorScheme,
style,
light: lightConfig,
dark: darkConfig,
meta: {
description,
author: 'roadkit',
version: '1.0.0',
accessible: true, // All presets are designed to meet WCAG AA
tags: ['preset', 'shadcn', colorScheme],
},
};
}
/**
* Blue theme - Default shadcn/ui blue theme
* Professional and trustworthy, excellent for business applications
*/
export const blueTheme: Theme = createTheme(
'blue',
'Blue',
'blue',
'new-york',
221, // Blue hue
83, // High saturation for vibrant blue
'Professional blue theme perfect for business applications'
);
/**
* Green theme - Natural and calming
* Great for environmental, health, or growth-focused applications
*/
export const greenTheme: Theme = createTheme(
'green',
'Green',
'green',
'new-york',
142, // Green hue
71, // Balanced saturation
'Fresh green theme ideal for eco-friendly and health applications'
);
/**
* Orange theme - Energetic and warm
* Perfect for creative and energetic brand identities
*/
export const orangeTheme: Theme = createTheme(
'orange',
'Orange',
'orange',
'new-york',
25, // Orange hue
95, // High saturation for vibrant orange
'Vibrant orange theme for creative and energetic brands'
);
/**
* Red theme - Bold and attention-grabbing
* Suitable for alerts, warnings, or bold brand statements
*/
export const redTheme: Theme = createTheme(
'red',
'Red',
'red',
'new-york',
0, // Pure red hue
84, // High saturation
'Bold red theme for attention-grabbing interfaces'
);
/**
* Rose theme - Elegant and sophisticated
* Perfect for fashion, beauty, or premium brand experiences
*/
export const roseTheme: Theme = createTheme(
'rose',
'Rose',
'rose',
'new-york',
350, // Rose/pink hue
89, // High saturation
'Elegant rose theme for sophisticated and premium brands'
);
/**
* Stone theme - Warm neutral
* Excellent for content-heavy applications with a warm, approachable feel
*/
export const stoneTheme: Theme = createTheme(
'stone',
'Stone',
'stone',
'new-york',
25, // Warm hue
5, // Low saturation for neutral feel
'Warm neutral stone theme for content-focused applications'
);
/**
* Slate theme - Cool neutral
* Modern and clean, perfect for technical or professional interfaces
*/
export const slateTheme: Theme = createTheme(
'slate',
'Slate',
'slate',
'new-york',
215, // Cool blue-gray hue
14, // Low saturation
'Modern slate theme for clean, professional interfaces'
);
/**
* Gray theme - Pure neutral
* Versatile and timeless, works for any application type
*/
export const grayTheme: Theme = createTheme(
'gray',
'Gray',
'gray',
'new-york',
0, // No hue (pure gray)
0, // No saturation
'Timeless gray theme that works for any application'
);
/**
* Neutral theme - Balanced neutral
* Similar to gray but with slight warmth, very versatile
*/
export const neutralTheme: Theme = createTheme(
'neutral',
'Neutral',
'neutral',
'new-york',
0, // No hue
0, // No saturation
'Balanced neutral theme with universal appeal'
);
/**
* Zinc theme - Modern neutral
* Contemporary and clean with a slightly cool feel
*/
export const zincTheme: Theme = createTheme(
'zinc',
'Zinc',
'zinc',
'new-york',
240, // Cool hue
5, // Very low saturation
'Modern zinc theme with a clean, contemporary feel'
);
/**
* Violet theme - Creative and modern
* Perfect for creative agencies, startups, or innovative products
*/
export const violetTheme: Theme = createTheme(
'violet',
'Violet',
'violet',
'new-york',
263, // Violet hue
70, // Moderate saturation
'Creative violet theme for innovative and modern brands'
);
/**
* Yellow theme - Bright and optimistic
* Great for educational, children's, or happiness-focused applications
*/
export const yellowTheme: Theme = createTheme(
'yellow',
'Yellow',
'yellow',
'new-york',
48, // Yellow hue
96, // High saturation
'Bright yellow theme for optimistic and cheerful interfaces'
);
/**
* Collection of all preset themes
* These are the themes available out-of-the-box with roadkit
*/
export const presetThemes: Theme[] = [
blueTheme,
greenTheme,
orangeTheme,
redTheme,
roseTheme,
stoneTheme,
slateTheme,
grayTheme,
neutralTheme,
zincTheme,
violetTheme,
yellowTheme,
];
/**
* Default theme used when no specific theme is selected
* Blue theme is chosen as it's professional and widely accepted
*/
export const defaultTheme: Theme = blueTheme;
/**
* Get theme by ID from presets
* @param id - Theme identifier
* @returns Theme if found, undefined otherwise
*/
export function getPresetTheme(id: string): Theme | undefined {
return presetThemes.find(theme => theme.id === id);
}
/**
* Get all available theme IDs
* @returns Array of theme identifiers
*/
export function getPresetThemeIds(): string[] {
return presetThemes.map(theme => theme.id);
}
/**
* Get themes by color scheme
* @param colorScheme - Color scheme to filter by
* @returns Array of themes matching the color scheme
*/
export function getThemesByColorScheme(colorScheme: string): Theme[] {
return presetThemes.filter(theme => theme.colorScheme === colorScheme);
}