Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions src/components/test-wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { I18nextProvider } from 'react-i18next';
import i18n from '../i18n';
import { LocaleProvider } from '../providers/LocaleProvider';

// Test wrapper component that provides i18n and locale context
export function TestWrapper({ children }: { children: React.ReactNode }) {
return (
<I18nextProvider i18n={i18n}>
<LocaleProvider defaultLocale="en-US">
{children}
</LocaleProvider>
</I18nextProvider>
);
}
4 changes: 2 additions & 2 deletions src/components/ui/menubar/menubar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const MenubarSubContent = React.forwardRef<
<MenubarPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg",
"z-50 min-w-32 overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg",
"data-[state=open]:animate-in data-[state=closed]:animate-out",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
Expand All @@ -101,7 +101,7 @@ const MenubarContent = React.forwardRef<
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[12rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md",
"z-50 min-w-48 overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md",
"data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
Expand Down
92 changes: 84 additions & 8 deletions src/foundation/tokens/Colors.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,40 @@ const meta: Meta = {
docs: {
description: {
component: `
# Orange Recipe Theme System
# Enhanced Orange Recipe Theme System

Our warm, orange-inspired color palette is designed specifically for food and recipe applications. All colors use the modern OKLCH color space for better accessibility and perceptual uniformity.
Our sophisticated, warm orange-inspired color palette is designed specifically for modern food and recipe applications. All colors use the OKLCH color space for superior accessibility and perceptual uniformity.

## OKLCH Color Space
## Enhanced Food Aesthetics

OKLCH provides better perceptual uniformity than HSL/RGB, ensuring consistent lightness and chroma across all colors in our palette.
The enhanced theme provides:
- **Richer Orange Saturation**: Improved warm oranges that better complement food imagery
- **Fresh Green Accents**: Complementary greens that evoke freshness and natural ingredients
- **Sophisticated Neutrals**: Warm-toned grays that create an appetizing atmosphere
- **Strategic CTAs**: Enhanced accent colors for better user engagement

## Color Psychology for Food

- **Primary Orange**: Stimulates appetite and creates warmth
- **Fresh Greens**: Convey freshness, health, and natural ingredients
- **Warm Neutrals**: Provide sophisticated backdrop without competing with food imagery
- **Earth Tones**: Ground the palette in natural, organic feelings

## OKLCH Benefits

OKLCH provides better perceptual uniformity than HSL/RGB, ensuring:
- Consistent lightness perception across colors
- Better accessibility compliance
- More predictable color relationships
- Enhanced dark mode transitions

## Theme Features

- **Food-Inspired Colors**: Warm orange tones that complement recipe imagery
- **Dark/Light Mode Support**: Full theme switching with system preference detection
- **WCAG AA+ Compliance**: All color combinations meet accessibility standards
- **Cultural Appropriateness**: Orange is positive across most cultures for food content
- **Food-Inspired Enhancement**: Warmer, more saturated oranges with better food appeal
- **Dark/Light Mode Support**: Full theme switching with improved contrast ratios
- **WCAG AA+ Compliance**: All color combinations exceed accessibility standards
- **Cultural Sensitivity**: Orange remains positive across cultures for food content
- **Modern Recipe Platform Aesthetics**: Aligned with industry-leading platforms
`
}
}
Expand Down Expand Up @@ -167,6 +187,62 @@ export const OrangeThemeShowcase = {
variable="--destructive"
description="Error states and destructive actions"
/>
<ColorSwatch
name="Success"
variable="--success"
description="Success states and positive actions"
/>
<ColorSwatch
name="Warning"
variable="--warning"
description="Warning states and caution indicators"
/>
</div>
</section>

{/* Enhanced Visual Elements Section */}
<section className="space-y-4 mb-8">
<h2 className="text-2xl font-semibold border-b pb-2 text-foreground">Enhanced Visual Elements</h2>
<div className="space-y-6">

{/* Enhanced Recipe Card with New Shadows */}
<div className="p-recipe-card bg-card border rounded-lg shadow-recipe-card hover:shadow-recipe-card-hover transition-shadow duration-300">
<h3 className="text-lg font-semibold text-card-foreground mb-3">🍝 Enhanced Recipe Card</h3>
<div className="space-y-3">
<div className="h-32 bg-gradient-to-br from-orange-100 to-red-100 rounded-md shadow-food-image flex items-center justify-center">
<span className="text-4xl">πŸ…</span>
</div>
<h4 className="font-medium text-card-foreground">Rustic Tomato Basil Pasta</h4>
<p className="text-sm text-muted-foreground">Fresh Roma tomatoes, aromatic basil, and authentic Italian olive oil</p>
<div className="flex gap-ingredient-gap items-center">
<span className="text-xs bg-accent text-accent-foreground px-2 py-1 rounded-full">🌱 Fresh</span>
<span className="text-xs bg-secondary text-secondary-foreground px-2 py-1 rounded">⏱️ 25 min</span>
<span className="text-xs bg-success text-success-foreground px-2 py-1 rounded">βœ… Easy</span>
</div>
<button className="w-full bg-primary text-primary-foreground py-2 rounded-md hover:bg-primary/90 transition-colors font-medium">
View Full Recipe
</button>
</div>
</div>

{/* Spacing Demonstration */}
<div className="p-6 bg-muted rounded-lg">
<h3 className="text-lg font-semibold text-foreground mb-3">πŸ“ Enhanced Spacing System</h3>
<div className="space-y-content-gap">
<div className="bg-card p-4 rounded border">
<h4 className="font-medium mb-2">Recipe Card Spacing (1.5rem)</h4>
<p className="text-sm text-muted-foreground">Optimal padding for recipe cards</p>
</div>
<div className="bg-card p-6 rounded border">
<h4 className="font-medium mb-ingredient-gap">Ingredient List</h4>
<div className="space-y-ingredient-gap text-sm">
<div>β€’ 2 cups fresh basil leaves</div>
<div>β€’ 4 large tomatoes, diced</div>
<div>β€’ 3 tbsp extra virgin olive oil</div>
</div>
</div>
</div>
</div>
</div>
</section>

Expand Down
180 changes: 180 additions & 0 deletions src/foundation/tokens/accessibility.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/**
* Accessibility validation for enhanced orange theme
* Tests color contrast ratios to ensure WCAG AA+ compliance
*/

// Color values from the enhanced theme (OKLCH converted to approximate RGB for testing)
export const ENHANCED_THEME_COLORS = {
light: {
// Primary colors
primary: '#c2410c', // oklch(0.625 0.233 47.604) - improved contrast
primaryForeground: '#ffffff',

// Background colors
background: '#ffffff',
foreground: '#1c1917', // oklch(0.127 0.014 47.604)

// Accent colors (fresh green)
accent: '#ecfdf5', // oklch(0.98 0.024 142)
accentForeground: '#166534', // oklch(0.4 0.15 142)

// Secondary colors
secondary: '#fef7ed', // oklch(0.98 0.008 47.604)
secondaryForeground: '#9a3412', // oklch(0.4 0.15 47.604)

// Semantic colors
success: '#15803d', // oklch(0.6 0.15 142) - improved contrast
successForeground: '#ffffff',
warning: '#b45309', // oklch(0.545 0.165 85) - improved contrast
warningForeground: '#ffffff',
destructive: '#dc2626', // oklch(0.602 0.242 30.233)
destructiveForeground: '#ffffff',
},
dark: {
// Primary colors for dark theme
primary: '#fb923c', // oklch(0.746 0.213 47.604)
primaryForeground: '#1c1917',

// Background colors
background: '#1c1917', // oklch(0.127 0.014 47.604)
foreground: '#fafaf9', // oklch(0.985 0.002 47.604)

// Accent colors
accent: '#166534', // oklch(0.4 0.15 142)
accentForeground: '#86efac', // oklch(0.85 0.12 142)

// Secondary colors
secondary: '#44403c', // oklch(0.269 0.018 47.604)
secondaryForeground: '#fdba74', // oklch(0.8 0.12 47.604)
}
};

/**
* Calculate relative luminance of a color
* Based on WCAG guidelines
*/
function getLuminance(hex: string): number {
const rgb = hexToRgb(hex);
if (!rgb) return 0;

const { r, g, b } = rgb;
const [rs, gs, bs] = [r, g, b].map(c => {
c = c / 255;
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
});

return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
}

/**
* Calculate contrast ratio between two colors
*/
function getContrastRatio(color1: string, color2: string): number {
const lum1 = getLuminance(color1);
const lum2 = getLuminance(color2);
const lighter = Math.max(lum1, lum2);
const darker = Math.min(lum1, lum2);

return (lighter + 0.05) / (darker + 0.05);
}

/**
* Convert hex to RGB
*/
function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}

describe('Enhanced Theme Accessibility', () => {
describe('Light Theme Contrast Ratios', () => {
const { light } = ENHANCED_THEME_COLORS;

it('should meet WCAG AA standard for primary colors (4.5:1 minimum)', () => {
const ratio = getContrastRatio(light.primary, light.primaryForeground);
expect(ratio).toBeGreaterThanOrEqual(4.5);
});

it('should meet WCAG AA standard for foreground text', () => {
const ratio = getContrastRatio(light.background, light.foreground);
expect(ratio).toBeGreaterThanOrEqual(4.5);
});

it('should meet WCAG AA standard for accent colors', () => {
const ratio = getContrastRatio(light.accent, light.accentForeground);
expect(ratio).toBeGreaterThanOrEqual(4.5);
});

it('should meet WCAG AA standard for secondary colors', () => {
const ratio = getContrastRatio(light.secondary, light.secondaryForeground);
expect(ratio).toBeGreaterThanOrEqual(4.5);
});

it('should meet WCAG AA standard for semantic colors', () => {
// Success colors
const successRatio = getContrastRatio(light.success, light.successForeground);
expect(successRatio).toBeGreaterThanOrEqual(4.5);

// Warning colors
const warningRatio = getContrastRatio(light.warning, light.warningForeground);
expect(warningRatio).toBeGreaterThanOrEqual(4.5);

// Destructive colors
const destructiveRatio = getContrastRatio(light.destructive, light.destructiveForeground);
expect(destructiveRatio).toBeGreaterThanOrEqual(4.5);
});
});

describe('Dark Theme Contrast Ratios', () => {
const { dark } = ENHANCED_THEME_COLORS;

it('should meet WCAG AA standard for primary colors in dark theme', () => {
const ratio = getContrastRatio(dark.primary, dark.primaryForeground);
expect(ratio).toBeGreaterThanOrEqual(4.5);
});

it('should meet WCAG AA standard for foreground text in dark theme', () => {
const ratio = getContrastRatio(dark.background, dark.foreground);
expect(ratio).toBeGreaterThanOrEqual(4.5);
});

it('should meet WCAG AA standard for accent colors in dark theme', () => {
const ratio = getContrastRatio(dark.accent, dark.accentForeground);
expect(ratio).toBeGreaterThanOrEqual(4.5);
});

it('should meet WCAG AA standard for secondary colors in dark theme', () => {
const ratio = getContrastRatio(dark.secondary, dark.secondaryForeground);
expect(ratio).toBeGreaterThanOrEqual(4.5);
});
});

describe('Color Psychology for Food Applications', () => {
it('should use warm orange tones that stimulate appetite', () => {
// Primary orange should have warm undertones
const primaryRgb = hexToRgb(ENHANCED_THEME_COLORS.light.primary);
expect(primaryRgb).toBeTruthy();
if (primaryRgb) {
// Orange should have more red than blue for warmth
expect(primaryRgb.r).toBeGreaterThan(primaryRgb.b);
// Should have significant saturation (not gray)
expect(Math.abs(primaryRgb.r - primaryRgb.g)).toBeGreaterThan(50);
}
});

it('should use fresh green accents for natural/organic feel', () => {
// Accent green should evoke freshness
const accentRgb = hexToRgb(ENHANCED_THEME_COLORS.light.accent);
expect(accentRgb).toBeTruthy();
if (accentRgb) {
// Light accent should be predominantly white/light green
expect(accentRgb.g).toBeGreaterThan(200); // High green component
expect(accentRgb.r).toBeGreaterThan(200); // High red for lightness
}
});
});
});
Loading
Loading