Files
anthonyrawlins 7774d7ec98 feat(brand-system): Implement comprehensive CHORUS brand system with typography and design tokens
This commit implements a complete brand system overhaul including:

TYPOGRAPHY SYSTEM:
- Add Exo font family (Thin, Light, Regular, ExtraLight) as primary brand font
- Implement SF Pro Display/Text hierarchy for UI components
- Create comprehensive Typography component with all brand variants
- Update all components to use new typography tokens

DESIGN TOKEN SYSTEM:
- Create complete design token system in theme/designTokens.ts
- Define Carbon Black (#1a1a1a), Walnut Brown (#8B4513), Brushed Aluminum (#A8A8A8) palette
- Implement CSS custom properties for consistent theming
- Update Ant Design theme integration

COMPONENT UPDATES:
- Enhance Hero section with Exo Thin typography and improved layout
- Update navigation with SF Pro font hierarchy
- Redesign Button component with new variants and accessibility
- Apply brand colors and typography across all showcase sections
- Improve Footer with consistent brand application

PERFORMANCE & ACCESSIBILITY:
- Self-host Exo fonts for optimal loading performance
- Implement proper font-display strategies
- Add comprehensive accessibility audit documentation
- Include responsive testing verification

DOCUMENTATION:
- Add brand system demo and implementation guides
- Include QA testing reports and accessibility audits
- Document design token usage and component patterns

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-02 22:12:42 +10:00

250 lines
6.9 KiB
TypeScript

'use client';
import React from 'react';
import { Button as AntButton, ButtonProps as AntButtonProps } from 'antd';
import { motion, MotionProps } from 'framer-motion';
import { cn } from '@/utils/cn';
import { Typography } from './Typography';
// Extend Ant Design ButtonProps with custom variants aligned to brand system
interface CustomButtonProps {
variant?: 'primary' | 'secondary' | 'tertiary' | 'ghost' | 'gradient' | 'walnut';
size?: 'small' | 'regular' | 'large';
fullWidth?: boolean;
animated?: boolean;
icon?: React.ReactNode;
iconPosition?: 'left' | 'right';
}
type ButtonProps = Omit<AntButtonProps, 'type' | 'size'> & CustomButtonProps & Partial<MotionProps>;
const MotionButton = motion(AntButton);
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'regular',
fullWidth = false,
animated = true,
icon,
iconPosition = 'left',
className,
children,
...antProps
}) => {
const getVariantStyles = () => {
const baseStyles = {
fontFamily: 'var(--font-body)',
fontWeight: 600,
lineHeight: 'var(--leading-tight)',
letterSpacing: 'var(--tracking-wider)',
borderRadius: 'var(--radius-button)',
transition: 'all var(--duration-normal) var(--easing-ease-out)',
cursor: 'pointer',
border: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: icon ? 'var(--space-sm)' : '0',
};
switch (variant) {
case 'primary':
return {
...baseStyles,
backgroundColor: 'var(--interactive-primary)',
color: '#ffffff',
boxShadow: 'var(--shadow-button)',
};
case 'secondary':
return {
...baseStyles,
backgroundColor: 'transparent',
color: 'var(--interactive-primary)',
border: '2px solid var(--interactive-primary)',
};
case 'tertiary':
return {
...baseStyles,
backgroundColor: 'var(--interactive-secondary)',
color: 'var(--text-inverse)',
};
case 'ghost':
return {
...baseStyles,
backgroundColor: 'transparent',
color: 'var(--text-secondary)',
border: '1px solid var(--border-secondary)',
};
case 'gradient':
return {
...baseStyles,
background: 'linear-gradient(135deg, var(--color-primary) 0%, var(--color-success) 100%)',
color: '#ffffff',
boxShadow: 'var(--shadow-button)',
};
case 'walnut':
return {
...baseStyles,
backgroundColor: 'var(--chorus-walnut-deep)',
color: '#ffffff',
boxShadow: 'var(--shadow-button)',
};
default:
return baseStyles;
}
};
const getSizeStyles = () => {
switch (size) {
case 'small':
return {
fontSize: 'var(--text-interface-small)',
padding: 'var(--space-sm) var(--space-md)',
minHeight: '36px',
};
case 'large':
return {
fontSize: 'var(--text-interface)',
padding: 'var(--space-lg) var(--space-2xl)',
minHeight: '52px',
};
default: // regular
return {
fontSize: 'var(--text-interface)',
padding: 'var(--space-md) var(--space-xl)',
minHeight: '44px',
};
}
};
const getHoverStyles = () => {
switch (variant) {
case 'primary':
return {
backgroundColor: 'var(--interactive-primary-hover)',
boxShadow: 'var(--shadow-button-hover)',
transform: 'translateY(-2px)',
};
case 'secondary':
return {
backgroundColor: 'var(--interactive-primary)',
color: '#ffffff',
transform: 'translateY(-2px)',
boxShadow: 'var(--shadow-button)',
};
case 'tertiary':
return {
backgroundColor: 'var(--interactive-secondary-hover)',
transform: 'translateY(-2px)',
boxShadow: 'var(--shadow-md)',
};
case 'ghost':
return {
color: 'var(--text-primary)',
borderColor: 'var(--border-primary)',
backgroundColor: 'var(--bg-tertiary)',
};
case 'gradient':
return {
boxShadow: 'var(--shadow-button-hover)',
transform: 'translateY(-2px)',
};
case 'walnut':
return {
backgroundColor: 'var(--chorus-walnut-medium)',
boxShadow: 'var(--shadow-button-hover)',
transform: 'translateY(-2px)',
};
default:
return {};
}
};
const buttonStyles = {
...getVariantStyles(),
...getSizeStyles(),
width: fullWidth ? '100%' : 'auto',
};
const animationProps = animated
? {
whileHover: getHoverStyles(),
whileTap: {
transform: 'translateY(0)',
boxShadow: variant === 'primary' || variant === 'gradient' || variant === 'walnut'
? 'var(--shadow-button)'
: undefined
},
transition: { duration: 0.2, ease: [0, 0, 0.2, 1] },
}
: {};
const renderContent = () => {
if (!icon) {
return <Typography.Button size={size}>{children}</Typography.Button>;
}
return (
<>
{iconPosition === 'left' && icon}
<Typography.Button size={size}>{children}</Typography.Button>
{iconPosition === 'right' && icon}
</>
);
};
if (animated) {
return (
<MotionButton
style={buttonStyles}
className={cn('focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] focus:ring-opacity-50', className)}
{...animationProps}
{...antProps}
>
{renderContent()}
</MotionButton>
);
}
return (
<AntButton
style={buttonStyles}
className={cn('focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] focus:ring-opacity-50', className)}
{...antProps}
>
{renderContent()}
</AntButton>
);
};
// Specific button variants for common use cases aligned to brand system
export const PrimaryButton: React.FC<Omit<ButtonProps, 'variant'>> = (props) => (
<Button variant="primary" {...props} />
);
export const SecondaryButton: React.FC<Omit<ButtonProps, 'variant'>> = (props) => (
<Button variant="secondary" {...props} />
);
export const TertiaryButton: React.FC<Omit<ButtonProps, 'variant'>> = (props) => (
<Button variant="tertiary" {...props} />
);
export const GhostButton: React.FC<Omit<ButtonProps, 'variant'>> = (props) => (
<Button variant="ghost" {...props} />
);
export const GradientButton: React.FC<Omit<ButtonProps, 'variant'>> = (props) => (
<Button variant="gradient" {...props} />
);
export const WalnutButton: React.FC<Omit<ButtonProps, 'variant'>> = (props) => (
<Button variant="walnut" {...props} />
);
// Legacy alias for backward compatibility
export const OutlineButton: React.FC<Omit<ButtonProps, 'variant'>> = (props) => (
<Button variant="secondary" {...props} />
);
export default Button;