Complete Phase 2 implementation with working unit converter: Core Conversion Engine (lib/units.ts): - Type-safe wrapper for convert-units library - Support for all 23 measures with TypeScript types - getAllMeasures() - Get all available categories - getUnitsForMeasure() - Get units for specific measure - getUnitInfo() - Get detailed unit information - convertUnit() - Convert between two units - convertToAll() - Convert to all compatible units - getCategoryColor() - Get Tailwind color class for measure - formatMeasureName() - Format measure names for display - searchUnits() - Fuzzy search across all units Utility Functions (lib/utils.ts): - cn() - Merge Tailwind classes with clsx and tailwind-merge - formatNumber() - Smart number formatting with scientific notation - debounce() - Debounce helper for inputs - parseNumberInput() - Parse user input to number - getRelativeTime() - Format timestamps UI Components: - Input - Styled input with focus states - Button - 6 variants (default, destructive, outline, secondary, ghost, link) - Card - Card container with header, title, description, content, footer Main Converter Component (components/converter/MainConverter.tsx): - Real-time conversion as user types - Category selection with 23 color-coded buttons - Input field with unit selector - Grid display of all conversions in selected measure - Color-coded result cards with category colors - Responsive layout (1/2/3 column grid) Homepage Updates: - Integrated MainConverter component - Clean header with gradient text - Uses design system colors Dependencies Added: - clsx - Class name utilities - tailwind-merge - Merge Tailwind classes intelligently Features Working: ✓ Select from 23 measurement categories ✓ Real-time conversion to all compatible units ✓ Color-coded categories ✓ Formatted number display ✓ Responsive design 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
107 lines
2.5 KiB
TypeScript
107 lines
2.5 KiB
TypeScript
/**
|
|
* Utility functions for the application
|
|
*/
|
|
|
|
import { type ClassValue, clsx } from 'clsx';
|
|
import { twMerge } from 'tailwind-merge';
|
|
|
|
/**
|
|
* Merge Tailwind CSS classes with clsx
|
|
*/
|
|
export function cn(...inputs: ClassValue[]) {
|
|
return twMerge(clsx(inputs));
|
|
}
|
|
|
|
/**
|
|
* Format a number for display with proper precision
|
|
*/
|
|
export function formatNumber(
|
|
value: number,
|
|
options: {
|
|
maxDecimals?: number;
|
|
minDecimals?: number;
|
|
notation?: 'standard' | 'scientific' | 'engineering' | 'compact';
|
|
} = {}
|
|
): string {
|
|
const {
|
|
maxDecimals = 6,
|
|
minDecimals = 0,
|
|
notation = 'standard',
|
|
} = options;
|
|
|
|
// Handle edge cases
|
|
if (!isFinite(value)) return value.toString();
|
|
if (value === 0) return '0';
|
|
|
|
// Use scientific notation for very large or very small numbers
|
|
const absValue = Math.abs(value);
|
|
const useScientific =
|
|
notation === 'scientific' ||
|
|
(notation === 'standard' && (absValue >= 1e10 || absValue < 1e-6));
|
|
|
|
if (useScientific) {
|
|
return value.toExponential(maxDecimals);
|
|
}
|
|
|
|
// Format with appropriate decimal places
|
|
const formatted = new Intl.NumberFormat('en-US', {
|
|
minimumFractionDigits: minDecimals,
|
|
maximumFractionDigits: maxDecimals,
|
|
notation: notation === 'compact' ? 'compact' : 'standard',
|
|
}).format(value);
|
|
|
|
return formatted;
|
|
}
|
|
|
|
/**
|
|
* Debounce function for input handling
|
|
*/
|
|
export function debounce<T extends (...args: any[]) => any>(
|
|
func: T,
|
|
wait: number
|
|
): (...args: Parameters<T>) => void {
|
|
let timeout: NodeJS.Timeout | null = null;
|
|
|
|
return function executedFunction(...args: Parameters<T>) {
|
|
const later = () => {
|
|
timeout = null;
|
|
func(...args);
|
|
};
|
|
|
|
if (timeout) clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Parse a number input string
|
|
*/
|
|
export function parseNumberInput(input: string): number | null {
|
|
if (!input || input.trim() === '') return null;
|
|
|
|
// Remove spaces and replace comma with dot
|
|
const cleaned = input.replace(/\s/g, '').replace(',', '.');
|
|
|
|
const parsed = parseFloat(cleaned);
|
|
|
|
return isNaN(parsed) ? null : parsed;
|
|
}
|
|
|
|
/**
|
|
* Get relative time from timestamp
|
|
*/
|
|
export function getRelativeTime(timestamp: number): string {
|
|
const now = Date.now();
|
|
const diff = now - timestamp;
|
|
|
|
const seconds = Math.floor(diff / 1000);
|
|
const minutes = Math.floor(seconds / 60);
|
|
const hours = Math.floor(minutes / 60);
|
|
const days = Math.floor(hours / 24);
|
|
|
|
if (days > 0) return `${days}d ago`;
|
|
if (hours > 0) return `${hours}h ago`;
|
|
if (minutes > 0) return `${minutes}m ago`;
|
|
return 'just now';
|
|
}
|