From 901d9047e22446f88c12414a29f70ea199debfe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Sat, 8 Nov 2025 09:34:57 +0100 Subject: [PATCH] feat: implement Phase 2 - Core conversion engine and UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- app/page.tsx | 28 ++-- components/converter/MainConverter.tsx | 144 ++++++++++++++++ components/ui/button.tsx | 46 ++++++ components/ui/card.tsx | 78 +++++++++ components/ui/input.tsx | 28 ++++ lib/units.ts | 218 +++++++++++++++++++++++++ lib/utils.ts | 106 ++++++++++++ package.json | 4 +- pnpm-lock.yaml | 17 ++ 9 files changed, 652 insertions(+), 17 deletions(-) create mode 100644 components/converter/MainConverter.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/card.tsx create mode 100644 components/ui/input.tsx create mode 100644 lib/units.ts create mode 100644 lib/utils.ts diff --git a/app/page.tsx b/app/page.tsx index e3dada3..3671909 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,23 +1,19 @@ +import MainConverter from '@/components/converter/MainConverter'; + export default function Home() { return ( -
+
-
-
-

- Unit Converter -

-

- Convert between 187 units across 23 measurement categories -

-
+
+

+ Unit Converter +

+

+ Convert between 187 units across 23 measurement categories +

+
-
-

- Coming soon: Real-time bidirectional conversion with innovative UX -

-
-
+
); diff --git a/components/converter/MainConverter.tsx b/components/converter/MainConverter.tsx new file mode 100644 index 0000000..4cd58d5 --- /dev/null +++ b/components/converter/MainConverter.tsx @@ -0,0 +1,144 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { + getAllMeasures, + getUnitsForMeasure, + convertToAll, + formatMeasureName, + getCategoryColor, + type Measure, + type ConversionResult, +} from '@/lib/units'; +import { parseNumberInput, formatNumber } from '@/lib/utils'; + +export default function MainConverter() { + const [selectedMeasure, setSelectedMeasure] = useState('length'); + const [selectedUnit, setSelectedUnit] = useState('m'); + const [inputValue, setInputValue] = useState('1'); + const [conversions, setConversions] = useState([]); + + const measures = getAllMeasures(); + const units = getUnitsForMeasure(selectedMeasure); + + // Update conversions when input changes + useEffect(() => { + const numValue = parseNumberInput(inputValue); + if (numValue !== null && selectedUnit) { + const results = convertToAll(numValue, selectedUnit); + setConversions(results); + } else { + setConversions([]); + } + }, [inputValue, selectedUnit]); + + // Update selected unit when measure changes + useEffect(() => { + const availableUnits = getUnitsForMeasure(selectedMeasure); + if (availableUnits.length > 0) { + setSelectedUnit(availableUnits[0]); + } + }, [selectedMeasure]); + + return ( +
+ {/* Category Selection */} + + + Select Category + + +
+ {measures.map((measure) => ( + + ))} +
+
+
+ + {/* Input Section */} + + + Convert {formatMeasureName(selectedMeasure)} + + +
+
+ + setInputValue(e.target.value)} + placeholder="Enter value" + className="text-lg" + /> +
+
+ + +
+
+
+
+ + {/* Results */} + + + Conversions + + +
+ {conversions.map((conversion) => ( +
+
+ {conversion.unitInfo.plural} +
+
+ {formatNumber(conversion.value)} +
+
+ {conversion.unit} +
+
+ ))} +
+
+
+
+ ); +} diff --git a/components/ui/button.tsx b/components/ui/button.tsx new file mode 100644 index 0000000..662a67f --- /dev/null +++ b/components/ui/button.tsx @@ -0,0 +1,46 @@ +import * as React from 'react'; +import { cn } from '@/lib/utils'; + +export interface ButtonProps + extends React.ButtonHTMLAttributes { + variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'; + size?: 'default' | 'sm' | 'lg' | 'icon'; +} + +const Button = React.forwardRef( + ({ className, variant = 'default', size = 'default', ...props }, ref) => { + return ( +