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>
250 lines
6.9 KiB
TypeScript
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; |