feat: implement Phase 2 - Core conversion engine and UI
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>
This commit is contained in:
106
lib/utils.ts
Normal file
106
lib/utils.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 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';
|
||||
}
|
||||
Reference in New Issue
Block a user