├── src ├── vite-env.d.ts ├── index.css ├── main.tsx ├── types │ └── calculator.ts ├── components │ ├── Display.tsx │ ├── CalculatorButton.tsx │ ├── ModeSelector.tsx │ ├── History.tsx │ └── calculators │ │ ├── StandardCalculator.tsx │ │ ├── GraphingCalculator.tsx │ │ ├── ConversionCalculator.tsx │ │ ├── FinancialCalculator.tsx │ │ └── ScientificCalculator.tsx ├── hooks │ └── useCalculator.ts └── App.tsx ├── .bolt ├── config.json └── prompt ├── public ├── black_circle_360x360.png └── white_circle_360x360.png ├── postcss.config.js ├── tsconfig.json ├── tailwind.config.js ├── vite.config.ts ├── .gitignore ├── tsconfig.node.json ├── index.html ├── tsconfig.app.json ├── eslint.config.js ├── package.json ├── LICENSE └── README.md /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.bolt/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "bolt-vite-react-ts" 3 | } 4 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /public/black_circle_360x360.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seehiong/swift-calc/HEAD/public/black_circle_360x360.png -------------------------------------------------------------------------------- /public/white_circle_360x360.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seehiong/swift-calc/HEAD/public/white_circle_360x360.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | optimizeDeps: { 8 | exclude: ['lucide-react'], 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | import './index.css'; 5 | 6 | createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /.bolt/prompt: -------------------------------------------------------------------------------- 1 | For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production. 2 | 3 | By default, this template supports JSX syntax with Tailwind CSS classes, React hooks, and Lucide React for icons. Do not install other packages for UI themes, icons, etc unless absolutely necessary or I request them. 4 | 5 | Use icons from lucide-react for logos. 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .env 26 | 27 | # Local Netlify folder 28 | .netlify 29 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": ["ES2023"], 5 | "module": "ESNext", 6 | "skipLibCheck": true, 7 | 8 | /* Bundler mode */ 9 | "moduleResolution": "bundler", 10 | "allowImportingTsExtensions": true, 11 | "isolatedModules": true, 12 | "moduleDetection": "force", 13 | "noEmit": true, 14 | 15 | /* Linting */ 16 | "strict": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "noFallthroughCasesInSwitch": true 20 | }, 21 | "include": ["vite.config.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | SwiftCalc | Professional Calculator Suite 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"] 24 | } 25 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import globals from 'globals'; 3 | import reactHooks from 'eslint-plugin-react-hooks'; 4 | import reactRefresh from 'eslint-plugin-react-refresh'; 5 | import tseslint from 'typescript-eslint'; 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | } 28 | ); 29 | -------------------------------------------------------------------------------- /src/types/calculator.ts: -------------------------------------------------------------------------------- 1 | export type CalculatorMode = 'standard' | 'scientific' | 'graphing' | 'financial' | 'conversion'; 2 | 3 | export interface CalculationHistory { 4 | id: string; 5 | expression: string; 6 | result: string; 7 | timestamp: Date; 8 | mode: CalculatorMode; 9 | } 10 | 11 | export interface FinancialCalculation { 12 | type: 'loan' | 'compound' | 'investment'; 13 | principal?: number; 14 | rate?: number; 15 | time?: number; 16 | payment?: number; 17 | futureValue?: number; 18 | presentValue?: number; 19 | } 20 | 21 | export interface ConversionUnit { 22 | name: string; 23 | symbol: string; 24 | factor: number; 25 | } 26 | 27 | export interface ConversionCategory { 28 | name: string; 29 | units: ConversionUnit[]; 30 | } 31 | 32 | export interface GraphFunction { 33 | id: string; 34 | expression: string; 35 | color: string; 36 | visible: boolean; 37 | } -------------------------------------------------------------------------------- /src/components/Display.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface DisplayProps { 4 | value: string; 5 | memory: number; 6 | expression?: string; 7 | } 8 | 9 | export const Display: React.FC = ({ value, memory, expression }) => { 10 | return ( 11 |
12 | {expression && ( 13 |
14 | {expression} 15 |
16 | )} 17 |
18 |
19 | {value} 20 |
21 | {memory !== 0 && ( 22 |
23 | M: {memory} 24 |
25 | )} 26 |
27 |
28 | ); 29 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swift-calc", 3 | "private": true, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "lucide-react": "^0.344.0", 14 | "react": "^18.3.1", 15 | "react-dom": "^18.3.1", 16 | "mathjs": "^12.4.0", 17 | "chart.js": "^4.4.0", 18 | "react-chartjs-2": "^5.2.0", 19 | "chartjs-adapter-date-fns": "^3.0.0" 20 | }, 21 | "devDependencies": { 22 | "@eslint/js": "^9.9.1", 23 | "@types/react": "^18.3.5", 24 | "@types/react-dom": "^18.3.0", 25 | "@vitejs/plugin-react": "^4.3.1", 26 | "autoprefixer": "^10.4.18", 27 | "eslint": "^9.9.1", 28 | "eslint-plugin-react-hooks": "^5.1.0-rc.0", 29 | "eslint-plugin-react-refresh": "^0.4.11", 30 | "globals": "^15.9.0", 31 | "postcss": "^8.4.35", 32 | "tailwindcss": "^3.4.1", 33 | "typescript": "^5.5.3", 34 | "typescript-eslint": "^8.3.0", 35 | "vite": "^5.4.2" 36 | } 37 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 SwiftCalc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/components/CalculatorButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface CalculatorButtonProps { 4 | value: string; 5 | onClick: (value: string) => void; 6 | className?: string; 7 | children?: React.ReactNode; 8 | disabled?: boolean; 9 | variant?: 'default' | 'operator' | 'equals' | 'clear' | 'memory' | 'function'; 10 | } 11 | 12 | export const CalculatorButton: React.FC = ({ 13 | value, 14 | onClick, 15 | className = '', 16 | children, 17 | disabled = false, 18 | variant = 'default', 19 | }) => { 20 | const baseClasses = 'h-12 rounded-lg font-medium transition-all duration-200 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed'; 21 | 22 | const variantClasses = { 23 | default: 'bg-white/80 hover:bg-white text-gray-800 border border-gray-200 hover:border-gray-300 shadow-sm hover:shadow-md', 24 | operator: 'bg-blue-500 hover:bg-blue-600 text-white shadow-md hover:shadow-lg', 25 | equals: 'bg-green-500 hover:bg-green-600 text-white shadow-md hover:shadow-lg', 26 | clear: 'bg-red-500 hover:bg-red-600 text-white shadow-md hover:shadow-lg', 27 | memory: 'bg-purple-500 hover:bg-purple-600 text-white shadow-md hover:shadow-lg', 28 | function: 'bg-indigo-500 hover:bg-indigo-600 text-white shadow-md hover:shadow-lg text-sm', 29 | }; 30 | 31 | return ( 32 | 39 | ); 40 | }; -------------------------------------------------------------------------------- /src/components/ModeSelector.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Calculator, Sigma, TrendingUp, DollarSign, ArrowLeftRight } from 'lucide-react'; 3 | import { CalculatorMode } from '../types/calculator'; 4 | 5 | interface ModeSelectorProps { 6 | currentMode: CalculatorMode; 7 | onModeChange: (mode: CalculatorMode) => void; 8 | } 9 | 10 | const modes: { id: CalculatorMode; label: string; icon: React.ReactNode }[] = [ 11 | { id: 'standard', label: 'Standard', icon: }, 12 | { id: 'scientific', label: 'Scientific', icon: }, 13 | { id: 'graphing', label: 'Graphing', icon: }, 14 | { id: 'financial', label: 'Financial', icon: }, 15 | { id: 'conversion', label: 'Convert', icon: }, 16 | ]; 17 | 18 | export const ModeSelector: React.FC = ({ currentMode, onModeChange }) => { 19 | return ( 20 |
21 | {modes.map((mode) => ( 22 | 34 | ))} 35 |
36 | ); 37 | }; -------------------------------------------------------------------------------- /src/hooks/useCalculator.ts: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | import { evaluate } from 'mathjs'; 3 | import { CalculationHistory, CalculatorMode } from '../types/calculator'; 4 | 5 | export const useCalculator = () => { 6 | const [display, setDisplay] = useState('0'); 7 | const [memory, setMemory] = useState(0); 8 | const [lastResult, setLastResult] = useState('0'); 9 | const [history, setHistory] = useState([]); 10 | const [mode, setMode] = useState('standard'); 11 | const [isDarkMode, setIsDarkMode] = useState(false); 12 | 13 | const addToHistory = useCallback((expression: string, result: string) => { 14 | const newEntry: CalculationHistory = { 15 | id: Date.now().toString(), 16 | expression, 17 | result, 18 | timestamp: new Date(), 19 | mode, 20 | }; 21 | setHistory(prev => [newEntry, ...prev.slice(0, 49)]); // Keep last 50 entries 22 | }, [mode]); 23 | 24 | // Calculate function that automatically adds to history (for simple calculations) 25 | const calculate = useCallback((expression: string) => { 26 | try { 27 | console.log('Calculating:', expression); 28 | const result = evaluate(expression); 29 | console.log('Result:', result); 30 | 31 | let resultStr: string; 32 | if (typeof result === 'number') { 33 | if (Number.isInteger(result)) { 34 | // Format integers with commas for readability 35 | resultStr = result.toLocaleString(); 36 | } else { 37 | // Format decimals, remove trailing zeros, then add commas 38 | const formatted = result.toFixed(8).replace(/\.?0+$/, ''); 39 | const num = parseFloat(formatted); 40 | resultStr = num.toLocaleString(); 41 | } 42 | } else { 43 | resultStr = result.toString(); 44 | } 45 | 46 | setDisplay(resultStr); 47 | setLastResult(resultStr); 48 | return resultStr; 49 | } catch (error) { 50 | console.error('Calculation error:', error); 51 | setDisplay('Error'); 52 | return 'Error'; 53 | } 54 | }, []); 55 | 56 | // Calculate function that doesn't add to history (for manual history control) 57 | const calculateOnly = useCallback((expression: string) => { 58 | try { 59 | console.log('Calculating:', expression); 60 | const result = evaluate(expression); 61 | console.log('Result:', result); 62 | 63 | let resultStr: string; 64 | if (typeof result === 'number') { 65 | if (Number.isInteger(result)) { 66 | resultStr = result.toLocaleString(); 67 | } else { 68 | const formatted = result.toFixed(8).replace(/\.?0+$/, ''); 69 | const num = parseFloat(formatted); 70 | resultStr = num.toLocaleString(); 71 | } 72 | } else { 73 | resultStr = result.toString(); 74 | } 75 | 76 | return resultStr; 77 | } catch (error) { 78 | console.error('Calculation error:', error); 79 | return 'Error'; 80 | } 81 | }, []); 82 | 83 | const clearDisplay = useCallback(() => { 84 | setDisplay('0'); 85 | }, []); 86 | 87 | const clearAll = useCallback(() => { 88 | setDisplay('0'); 89 | }, []); 90 | 91 | const memoryAdd = useCallback(() => { 92 | setMemory(prev => prev + parseFloat(display || '0')); 93 | }, [display]); 94 | 95 | const memorySubtract = useCallback(() => { 96 | setMemory(prev => prev - parseFloat(display || '0')); 97 | }, [display]); 98 | 99 | const memoryRecall = useCallback(() => { 100 | setDisplay(memory.toString()); 101 | }, [memory]); 102 | 103 | const memoryClear = useCallback(() => { 104 | setMemory(0); 105 | }, []); 106 | 107 | const clearHistory = useCallback(() => { 108 | setHistory([]); 109 | }, []); 110 | 111 | const toggleDarkMode = useCallback(() => { 112 | setIsDarkMode(prev => !prev); 113 | }, []); 114 | 115 | const recallLastResult = useCallback(() => { 116 | setDisplay(lastResult); 117 | }, [lastResult]); 118 | 119 | return { 120 | display, 121 | setDisplay, 122 | memory, 123 | history, 124 | mode, 125 | setMode, 126 | isDarkMode, 127 | lastResult, 128 | setLastResult, 129 | calculate, 130 | calculateOnly, 131 | clearDisplay, 132 | clearAll, 133 | memoryAdd, 134 | memorySubtract, 135 | memoryRecall, 136 | memoryClear, 137 | toggleDarkMode, 138 | addToHistory, 139 | clearHistory, 140 | recallLastResult, 141 | }; 142 | }; -------------------------------------------------------------------------------- /src/components/History.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { History as HistoryIcon, Trash2, Copy, Check } from 'lucide-react'; 3 | import { CalculationHistory } from '../types/calculator'; 4 | 5 | interface HistoryProps { 6 | history: CalculationHistory[]; 7 | onClearHistory: () => void; 8 | onSelectHistoryItem: (item: CalculationHistory) => void; 9 | } 10 | 11 | export const History: React.FC = ({ 12 | history, 13 | onClearHistory, 14 | onSelectHistoryItem 15 | }) => { 16 | const [expandedItem, setExpandedItem] = useState(null); 17 | const [copiedItem, setCopiedItem] = useState(null); 18 | 19 | const copyToClipboard = async (text: string, itemId: string) => { 20 | try { 21 | await navigator.clipboard.writeText(text); 22 | setCopiedItem(itemId); 23 | setTimeout(() => setCopiedItem(null), 2000); 24 | } catch (err) { 25 | console.error('Failed to copy text: ', err); 26 | } 27 | }; 28 | 29 | if (history.length === 0) { 30 | return ( 31 |
32 |
33 | 34 |

History

35 |
36 |
37 | 38 |

No calculations yet

39 |

Your calculation history will appear here

40 |
41 |
42 | ); 43 | } 44 | 45 | return ( 46 |
47 |
48 |
49 | 50 |

History

51 | ({history.length}) 52 | (Newest first) 53 |
54 | 61 |
62 | 63 |
64 | {history.map((item) => ( 65 |
69 |
70 |
71 | 82 |
83 | 94 |
95 |
96 | {item.mode.toUpperCase()} 97 |
98 |
99 | {item.timestamp.toLocaleString([], { 100 | month: 'short', 101 | day: 'numeric', 102 | hour: '2-digit', 103 | minute: '2-digit' 104 | })} 105 |
106 |
107 |
108 |
109 |
110 | 111 | {/* Expand/Collapse button for long expressions */} 112 | {item.expression.length > 50 && ( 113 | 119 | )} 120 |
121 | ))} 122 |
123 |
124 | ); 125 | }; -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Moon, Sun, Calculator as CalcIcon } from 'lucide-react'; 2 | import { useCalculator } from './hooks/useCalculator'; 3 | import { ModeSelector } from './components/ModeSelector'; 4 | import { StandardCalculator } from './components/calculators/StandardCalculator'; 5 | import { ScientificCalculator } from './components/calculators/ScientificCalculator'; 6 | import { GraphingCalculator } from './components/calculators/GraphingCalculator'; 7 | import { FinancialCalculator } from './components/calculators/FinancialCalculator'; 8 | import { ConversionCalculator } from './components/calculators/ConversionCalculator'; 9 | import { History } from './components/History'; 10 | 11 | function App() { 12 | const calculator = useCalculator(); 13 | const { 14 | display, 15 | setDisplay, 16 | memory, 17 | history, 18 | mode, 19 | setMode, 20 | isDarkMode, 21 | calculate, 22 | clearDisplay, 23 | clearAll, 24 | memoryAdd, 25 | memorySubtract, 26 | memoryRecall, 27 | memoryClear, 28 | toggleDarkMode, 29 | clearHistory, 30 | recallLastResult, 31 | addToHistory, 32 | } = calculator; 33 | 34 | const handleHistoryItemSelect = (item: any) => { 35 | setDisplay(item.result); 36 | }; 37 | 38 | return ( 39 |
43 |
44 | {/* Header */} 45 |
46 |
47 |
48 | 49 |
50 |
51 |

52 | SwiftCalc 53 |

54 |

55 | Professional calculator suite for all your needs 56 |

57 |
58 |
59 | 60 |
61 | 70 |
71 |
72 | 73 | {/* Mode Selector */} 74 | 75 | 76 |
77 | {/* Calculator */} 78 |
79 | {mode === 'standard' && ( 80 | 92 | )} 93 | 94 | {mode === 'scientific' && ( 95 | 105 | )} 106 | 107 | {mode === 'graphing' && ( 108 | 109 | )} 110 | 111 | {mode === 'financial' && ( 112 | 113 | )} 114 | 115 | {mode === 'conversion' && ( 116 | 117 | )} 118 |
119 | 120 | {/* History Panel */} 121 |
122 | 127 |
128 |
129 | 130 | {/* Footer */} 131 |
132 |

© 2025 SwiftCalc. Built with React, TypeScript & Tailwind CSS.

133 |

Professional-grade calculator suite for education, business, and personal use.

134 |
135 |
136 |
137 | ); 138 | } 139 | 140 | export default App; -------------------------------------------------------------------------------- /src/components/calculators/StandardCalculator.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { CalculatorButton } from '../CalculatorButton'; 3 | import { Display } from '../Display'; 4 | 5 | interface StandardCalculatorProps { 6 | display: string; 7 | setDisplay: (value: string) => void; 8 | memory: number; 9 | calculate: (expression: string) => string; 10 | clearDisplay: () => void; 11 | clearAll: () => void; 12 | memoryAdd: () => void; 13 | memorySubtract: () => void; 14 | memoryRecall: () => void; 15 | memoryClear: () => void; 16 | } 17 | 18 | export const StandardCalculator: React.FC = ({ 19 | display, 20 | setDisplay, 21 | memory, 22 | calculate, 23 | clearDisplay, 24 | clearAll, 25 | memoryAdd, 26 | memorySubtract, 27 | memoryRecall, 28 | memoryClear, 29 | }) => { 30 | const [expression, setExpression] = useState(''); 31 | const [waitingForOperand, setWaitingForOperand] = useState(false); 32 | 33 | const inputNumber = (num: string) => { 34 | if (waitingForOperand) { 35 | setDisplay(num); 36 | setWaitingForOperand(false); 37 | } else { 38 | setDisplay(display === '0' ? num : display + num); 39 | } 40 | }; 41 | 42 | const inputOperator = (nextOperator: string) => { 43 | const inputValue = parseFloat(display); 44 | 45 | if (expression === '') { 46 | setExpression(display + ' ' + nextOperator + ' '); 47 | } else { 48 | const result = calculate(expression + display); 49 | setExpression(result + ' ' + nextOperator + ' '); 50 | } 51 | 52 | setWaitingForOperand(true); 53 | }; 54 | 55 | const performCalculation = () => { 56 | if (expression !== '') { 57 | const result = calculate(expression + display); 58 | setDisplay(result); 59 | setExpression(''); 60 | setWaitingForOperand(true); 61 | } 62 | }; 63 | 64 | const inputDecimal = () => { 65 | if (waitingForOperand) { 66 | setDisplay('0.'); 67 | setWaitingForOperand(false); 68 | } else if (display.indexOf('.') === -1) { 69 | setDisplay(display + '.'); 70 | } 71 | }; 72 | 73 | const toggleSign = () => { 74 | if (display !== '0') { 75 | setDisplay(display.startsWith('-') ? display.slice(1) : '-' + display); 76 | } 77 | }; 78 | 79 | const percentage = () => { 80 | const value = parseFloat(display) / 100; 81 | setDisplay(value.toString()); 82 | }; 83 | 84 | const handleClear = () => { 85 | clearDisplay(); 86 | setExpression(''); 87 | setWaitingForOperand(false); 88 | }; 89 | 90 | const handleAllClear = () => { 91 | clearAll(); 92 | setExpression(''); 93 | setWaitingForOperand(false); 94 | }; 95 | 96 | return ( 97 |
98 | 99 | 100 |
101 | {/* Row 1 */} 102 | AC 103 | C 104 | ± 105 | inputOperator('/')} variant="operator">÷ 106 | 107 | {/* Row 2 */} 108 | MC 109 | MR 110 | M+ 111 | inputOperator('*')} variant="operator">× 112 | 113 | {/* Row 3 */} 114 | inputNumber('7')}>7 115 | inputNumber('8')}>8 116 | inputNumber('9')}>9 117 | inputOperator('-')} variant="operator">− 118 | 119 | {/* Row 4 */} 120 | inputNumber('4')}>4 121 | inputNumber('5')}>5 122 | inputNumber('6')}>6 123 | inputOperator('+')} variant="operator">+ 124 | 125 | {/* Row 5 */} 126 | inputNumber('1')}>1 127 | inputNumber('2')}>2 128 | inputNumber('3')}>3 129 | % 130 | 131 | {/* Row 6 */} 132 | inputNumber('0')} className="col-span-2">0 133 | . 134 | 135 | {/* Equals button */} 136 | = 137 |
138 |
139 | ); 140 | }; -------------------------------------------------------------------------------- /src/components/calculators/GraphingCalculator.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import { Line } from 'react-chartjs-2'; 3 | import { evaluate } from 'mathjs'; 4 | import { 5 | Chart as ChartJS, 6 | CategoryScale, 7 | LinearScale, 8 | PointElement, 9 | LineElement, 10 | Title, 11 | Tooltip, 12 | Legend, 13 | ChartOptions, 14 | } from 'chart.js'; 15 | import { GraphFunction } from '../../types/calculator'; 16 | import { Plus, Eye, EyeOff, Trash2 } from 'lucide-react'; 17 | 18 | ChartJS.register( 19 | CategoryScale, 20 | LinearScale, 21 | PointElement, 22 | LineElement, 23 | Title, 24 | Tooltip, 25 | Legend 26 | ); 27 | 28 | interface GraphingCalculatorProps { 29 | calculate: (expression: string) => string; 30 | } 31 | 32 | export const GraphingCalculator: React.FC = ({ calculate }) => { 33 | const [functions, setFunctions] = useState([ 34 | { id: '1', expression: 'x^2', color: '#3B82F6', visible: true }, 35 | ]); 36 | const [newFunction, setNewFunction] = useState(''); 37 | const [xMin, setXMin] = useState(-10); 38 | const [xMax, setXMax] = useState(10); 39 | const [yMin, setYMin] = useState(-10); 40 | const [yMax, setYMax] = useState(10); 41 | 42 | const colors = ['#3B82F6', '#EF4444', '#10B981', '#F59E0B', '#8B5CF6', '#EC4899']; 43 | 44 | const generateGraphData = () => { 45 | const step = (xMax - xMin) / 400; 46 | const xValues: number[] = []; 47 | for (let x = xMin; x <= xMax; x += step) { 48 | xValues.push(x); 49 | } 50 | 51 | const datasets = functions 52 | .filter(func => func.visible) 53 | .map((func, index) => { 54 | const yValues = xValues.map(x => { 55 | try { 56 | const expression = func.expression.replace(/x/g, `(${x})`); 57 | const result = evaluate(expression); 58 | return typeof result === 'number' && isFinite(result) ? result : null; 59 | } catch { 60 | return null; 61 | } 62 | }); 63 | 64 | return { 65 | label: `y = ${func.expression}`, 66 | data: yValues, 67 | borderColor: func.color, 68 | backgroundColor: func.color + '20', 69 | borderWidth: 2, 70 | fill: false, 71 | tension: 0, 72 | pointRadius: 0, 73 | pointHoverRadius: 4, 74 | }; 75 | }); 76 | 77 | return { 78 | labels: xValues.map(x => x.toFixed(2)), 79 | datasets, 80 | }; 81 | }; 82 | 83 | const chartOptions: ChartOptions<'line'> = { 84 | responsive: true, 85 | maintainAspectRatio: false, 86 | plugins: { 87 | legend: { 88 | position: 'top' as const, 89 | }, 90 | tooltip: { 91 | mode: 'index', 92 | intersect: false, 93 | }, 94 | }, 95 | scales: { 96 | x: { 97 | type: 'linear', 98 | position: 'center', 99 | min: xMin, 100 | max: xMax, 101 | grid: { 102 | color: 'rgba(0, 0, 0, 0.1)', 103 | }, 104 | ticks: { 105 | stepSize: (xMax - xMin) / 10, 106 | }, 107 | }, 108 | y: { 109 | type: 'linear', 110 | position: 'center', 111 | min: yMin, 112 | max: yMax, 113 | grid: { 114 | color: 'rgba(0, 0, 0, 0.1)', 115 | }, 116 | ticks: { 117 | stepSize: (yMax - yMin) / 10, 118 | }, 119 | }, 120 | }, 121 | interaction: { 122 | mode: 'nearest', 123 | axis: 'x', 124 | intersect: false, 125 | }, 126 | }; 127 | 128 | const addFunction = () => { 129 | if (newFunction.trim()) { 130 | const newFunc: GraphFunction = { 131 | id: Date.now().toString(), 132 | expression: newFunction.trim(), 133 | color: colors[functions.length % colors.length], 134 | visible: true, 135 | }; 136 | setFunctions([...functions, newFunc]); 137 | setNewFunction(''); 138 | } 139 | }; 140 | 141 | const removeFunction = (id: string) => { 142 | setFunctions(functions.filter(func => func.id !== id)); 143 | }; 144 | 145 | const toggleVisibility = (id: string) => { 146 | setFunctions(functions.map(func => 147 | func.id === id ? { ...func, visible: !func.visible } : func 148 | )); 149 | }; 150 | 151 | const updateExpression = (id: string, expression: string) => { 152 | setFunctions(functions.map(func => 153 | func.id === id ? { ...func, expression } : func 154 | )); 155 | }; 156 | 157 | const resetView = () => { 158 | setXMin(-10); 159 | setXMax(10); 160 | setYMin(-10); 161 | setYMax(10); 162 | }; 163 | 164 | return ( 165 |
166 | {/* Function Input */} 167 |
168 |
169 | setNewFunction(e.target.value)} 173 | placeholder="Enter function (e.g., x^2, sin(x), 2*x + 1)" 174 | className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" 175 | onKeyPress={(e) => e.key === 'Enter' && addFunction()} 176 | /> 177 | 184 |
185 | 186 | {/* Function List */} 187 |
188 | {functions.map((func) => ( 189 |
190 |
194 | updateExpression(func.id, e.target.value)} 198 | className="flex-1 px-2 py-1 border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500" 199 | /> 200 | 206 | 212 |
213 | ))} 214 |
215 |
216 | 217 | {/* View Controls */} 218 |
219 |
220 |
221 | 222 | setXMin(Number(e.target.value))} 226 | className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" 227 | /> 228 |
229 |
230 | 231 | setXMax(Number(e.target.value))} 235 | className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" 236 | /> 237 |
238 |
239 | 240 | setYMin(Number(e.target.value))} 244 | className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" 245 | /> 246 |
247 |
248 | 249 | setYMax(Number(e.target.value))} 253 | className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" 254 | /> 255 |
256 |
257 | 263 |
264 | 265 | {/* Graph */} 266 |
267 |
268 | 269 |
270 |
271 |
272 | ); 273 | }; -------------------------------------------------------------------------------- /src/components/calculators/ConversionCalculator.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { ConversionCategory } from '../../types/calculator'; 3 | 4 | interface ConversionCalculatorProps { 5 | calculate: (expression: string) => string; 6 | } 7 | 8 | const conversionCategories: ConversionCategory[] = [ 9 | { 10 | name: 'Length', 11 | units: [ 12 | { name: 'Meter', symbol: 'm', factor: 1 }, 13 | { name: 'Kilometer', symbol: 'km', factor: 1000 }, 14 | { name: 'Centimeter', symbol: 'cm', factor: 0.01 }, 15 | { name: 'Millimeter', symbol: 'mm', factor: 0.001 }, 16 | { name: 'Inch', symbol: 'in', factor: 0.0254 }, 17 | { name: 'Foot', symbol: 'ft', factor: 0.3048 }, 18 | { name: 'Yard', symbol: 'yd', factor: 0.9144 }, 19 | { name: 'Mile', symbol: 'mi', factor: 1609.34 }, 20 | ], 21 | }, 22 | { 23 | name: 'Weight', 24 | units: [ 25 | { name: 'Kilogram', symbol: 'kg', factor: 1 }, 26 | { name: 'Gram', symbol: 'g', factor: 0.001 }, 27 | { name: 'Pound', symbol: 'lb', factor: 0.453592 }, 28 | { name: 'Ounce', symbol: 'oz', factor: 0.0283495 }, 29 | { name: 'Stone', symbol: 'st', factor: 6.35029 }, 30 | { name: 'Ton', symbol: 't', factor: 1000 }, 31 | ], 32 | }, 33 | { 34 | name: 'Temperature', 35 | units: [ 36 | { name: 'Celsius', symbol: '°C', factor: 1 }, 37 | { name: 'Fahrenheit', symbol: '°F', factor: 1 }, 38 | { name: 'Kelvin', symbol: 'K', factor: 1 }, 39 | ], 40 | }, 41 | { 42 | name: 'Area', 43 | units: [ 44 | { name: 'Square Meter', symbol: 'm²', factor: 1 }, 45 | { name: 'Square Kilometer', symbol: 'km²', factor: 1000000 }, 46 | { name: 'Square Centimeter', symbol: 'cm²', factor: 0.0001 }, 47 | { name: 'Square Inch', symbol: 'in²', factor: 0.00064516 }, 48 | { name: 'Square Foot', symbol: 'ft²', factor: 0.092903 }, 49 | { name: 'Acre', symbol: 'acre', factor: 4046.86 }, 50 | { name: 'Hectare', symbol: 'ha', factor: 10000 }, 51 | ], 52 | }, 53 | { 54 | name: 'Volume', 55 | units: [ 56 | { name: 'Liter', symbol: 'L', factor: 1 }, 57 | { name: 'Milliliter', symbol: 'mL', factor: 0.001 }, 58 | { name: 'Gallon (US)', symbol: 'gal', factor: 3.78541 }, 59 | { name: 'Quart (US)', symbol: 'qt', factor: 0.946353 }, 60 | { name: 'Pint (US)', symbol: 'pt', factor: 0.473176 }, 61 | { name: 'Cup (US)', symbol: 'cup', factor: 0.236588 }, 62 | { name: 'Fluid Ounce (US)', symbol: 'fl oz', factor: 0.0295735 }, 63 | ], 64 | }, 65 | { 66 | name: 'Time', 67 | units: [ 68 | { name: 'Second', symbol: 's', factor: 1 }, 69 | { name: 'Minute', symbol: 'min', factor: 60 }, 70 | { name: 'Hour', symbol: 'h', factor: 3600 }, 71 | { name: 'Day', symbol: 'd', factor: 86400 }, 72 | { name: 'Week', symbol: 'wk', factor: 604800 }, 73 | { name: 'Month', symbol: 'mo', factor: 2629746 }, 74 | { name: 'Year', symbol: 'yr', factor: 31556952 }, 75 | ], 76 | }, 77 | ]; 78 | 79 | export const ConversionCalculator: React.FC = () => { 80 | const [selectedCategory, setSelectedCategory] = useState(conversionCategories[0]); 81 | const [fromUnit, setFromUnit] = useState(selectedCategory.units[0]); 82 | const [toUnit, setToUnit] = useState(selectedCategory.units[1]); 83 | const [inputValue, setInputValue] = useState('1'); 84 | const [result, setResult] = useState(''); 85 | 86 | const convertTemperature = (value: number, from: string, to: string): number => { 87 | // Convert to Celsius first 88 | let celsius: number; 89 | if (from === '°C') { 90 | celsius = value; 91 | } else if (from === '°F') { 92 | celsius = (value - 32) * 5/9; 93 | } else { // Kelvin 94 | celsius = value - 273.15; 95 | } 96 | 97 | // Convert from Celsius to target 98 | if (to === '°C') { 99 | return celsius; 100 | } else if (to === '°F') { 101 | return celsius * 9/5 + 32; 102 | } else { // Kelvin 103 | return celsius + 273.15; 104 | } 105 | }; 106 | 107 | const performConversion = () => { 108 | const value = parseFloat(inputValue); 109 | if (isNaN(value)) { 110 | setResult('Invalid input'); 111 | return; 112 | } 113 | 114 | if (selectedCategory.name === 'Temperature') { 115 | const converted = convertTemperature(value, fromUnit.symbol, toUnit.symbol); 116 | setResult(converted.toFixed(4).replace(/\.?0+$/, '')); 117 | } else { 118 | // Standard unit conversion using base factors 119 | const baseValue = value * fromUnit.factor; 120 | const converted = baseValue / toUnit.factor; 121 | setResult(converted.toFixed(8).replace(/\.?0+$/, '')); 122 | } 123 | }; 124 | 125 | const handleCategoryChange = (category: ConversionCategory) => { 126 | setSelectedCategory(category); 127 | setFromUnit(category.units[0]); 128 | setToUnit(category.units[1] || category.units[0]); 129 | setResult(''); 130 | }; 131 | 132 | React.useEffect(() => { 133 | if (inputValue && fromUnit && toUnit) { 134 | performConversion(); 135 | } 136 | }, [inputValue, fromUnit, toUnit]); 137 | 138 | const swapUnits = () => { 139 | const temp = fromUnit; 140 | setFromUnit(toUnit); 141 | setToUnit(temp); 142 | }; 143 | 144 | return ( 145 |
146 | {/* Category Selection */} 147 |
148 |

Conversion Categories

149 |
150 | {conversionCategories.map((category) => ( 151 | 162 | ))} 163 |
164 |
165 | 166 | {/* Conversion Interface */} 167 |
168 |

{selectedCategory.name} Conversion

169 | 170 |
171 | {/* From Unit */} 172 |
173 | 174 | setInputValue(e.target.value)} 178 | className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 mb-2" 179 | placeholder="Enter value" 180 | /> 181 | 192 |
193 | 194 | {/* Swap Button */} 195 |
196 | 202 |
203 | 204 | {/* To Unit */} 205 |
206 | 207 |
208 | 209 | {result || '0'} 210 | 211 |
212 | 223 |
224 |
225 | 226 | {/* Conversion Formula */} 227 | {result && ( 228 |
229 |

230 | Result: {inputValue} {fromUnit.symbol} = {result} {toUnit.symbol} 231 |

232 |
233 | )} 234 | 235 | {/* Quick Conversions */} 236 |
237 |

Quick Conversions

238 |
239 | {[1, 10, 100, 1000].map((quickValue) => { 240 | const quickResult = selectedCategory.name === 'Temperature' 241 | ? convertTemperature(quickValue, fromUnit.symbol, toUnit.symbol) 242 | : (quickValue * fromUnit.factor) / toUnit.factor; 243 | 244 | return ( 245 | 252 | ); 253 | })} 254 |
255 |
256 |
257 |
258 | ); 259 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftCalc 2 | 3 | A comprehensive, professional-grade calculator suite built with React, TypeScript, and Tailwind CSS. SwiftCalc provides multiple calculator modes including Standard, Scientific, Graphing, Financial, and Unit Conversion calculators - all in one swift, elegant interface. 4 | 5 | ![Calculator Suite](https://images.pexels.com/photos/6238297/pexels-photo-6238297.jpeg?auto=compress&cs=tinysrgb&w=1200&h=400&fit=crop) 6 | 7 | ## ✨ Features 8 | 9 | ### 🧮 Calculator Modes 10 | - **Standard Calculator**: Basic arithmetic operations with memory functions 11 | - **Scientific Calculator**: Advanced mathematical functions, trigonometry, logarithms, and more 12 | - **Graphing Calculator**: Plot mathematical functions with customizable viewing windows 13 | - **Financial Calculator**: Loan payments, compound interest, and investment calculations 14 | - **Unit Converter**: Convert between various units (length, weight, temperature, area, volume, time) 15 | 16 | ### 🎨 User Experience 17 | - **Dark/Light Mode**: Toggle between themes for comfortable viewing 18 | - **Calculation History**: Track and reuse previous calculations 19 | - **Responsive Design**: Works seamlessly on desktop, tablet, and mobile devices 20 | - **Professional UI**: Clean, modern interface with smooth animations 21 | - **Memory Functions**: Store and recall values across calculations 22 | 23 | ### 🔧 Technical Features 24 | - **Real-time Graphing**: Interactive function plotting with Chart.js 25 | - **Mathematical Expression Parsing**: Powered by Math.js for accurate calculations 26 | - **Type Safety**: Full TypeScript implementation 27 | - **Performance Optimized**: Efficient rendering and state management 28 | - **Accessibility**: Keyboard navigation and screen reader support 29 | 30 | ## 🚀 Getting Started 31 | 32 | ### Prerequisites 33 | - Node.js (version 16 or higher) 34 | - npm or yarn package manager 35 | 36 | ### Installation 37 | 38 | 1. **Clone the repository** 39 | ```bash 40 | git clone https://github.com/seehiong/swift-calc.git 41 | cd swift-calc 42 | ``` 43 | 44 | 2. **Install dependencies** 45 | ```bash 46 | npm install 47 | ``` 48 | 49 | 3. **Start the development server** 50 | ```bash 51 | npm run dev 52 | ``` 53 | 54 | 4. **Open your browser** 55 | Navigate to `http://localhost:5173` to view the application. 56 | 57 | ### Building for Production 58 | 59 | ```bash 60 | # Build the application 61 | npm run build 62 | 63 | # Preview the production build 64 | npm run preview 65 | ``` 66 | 67 | ## 📱 Usage Guide 68 | 69 | ### Standard Calculator 70 | - Basic arithmetic operations (+, -, ×, ÷) 71 | - Memory functions (MC, MR, M+, M-) 72 | - Percentage calculations 73 | - Sign toggle and decimal operations 74 | 75 | ### Scientific Calculator 76 | - Trigonometric functions (sin, cos, tan) with degree/radian modes 77 | - Logarithmic functions (log, ln) 78 | - Power and root operations 79 | - Constants (π, e) 80 | - Factorial and inverse functions 81 | 82 | ### Graphing Calculator 83 | - Plot multiple functions simultaneously 84 | - Customizable viewing window (X/Y min/max) 85 | - Function visibility toggle 86 | - Color-coded function lines 87 | - Interactive zoom and pan 88 | 89 | ### Financial Calculator 90 | - **Loan Calculator**: Calculate monthly payments, total interest 91 | - **Compound Interest**: Calculate growth with various compounding frequencies 92 | - **Investment Calculator**: Plan future value with regular contributions 93 | 94 | ### Unit Converter 95 | - **Length**: Meters, kilometers, inches, feet, miles, etc. 96 | - **Weight**: Kilograms, pounds, ounces, stones, etc. 97 | - **Temperature**: Celsius, Fahrenheit, Kelvin 98 | - **Area**: Square meters, acres, hectares, etc. 99 | - **Volume**: Liters, gallons, cups, fluid ounces, etc. 100 | - **Time**: Seconds, minutes, hours, days, years, etc. 101 | 102 | ## 🛠️ Technology Stack 103 | 104 | - **Frontend Framework**: React 18 105 | - **Language**: TypeScript 106 | - **Styling**: Tailwind CSS 107 | - **Mathematical Engine**: Math.js 108 | - **Charting**: Chart.js with React-Chart.js-2 109 | - **Icons**: Lucide React 110 | - **Build Tool**: Vite 111 | - **Linting**: ESLint with TypeScript support 112 | 113 | ## 📁 Project Structure 114 | 115 | ``` 116 | src/ 117 | ├── components/ 118 | │ ├── calculators/ 119 | │ │ ├── StandardCalculator.tsx 120 | │ │ ├── ScientificCalculator.tsx 121 | │ │ ├── GraphingCalculator.tsx 122 | │ │ ├── FinancialCalculator.tsx 123 | │ │ └── ConversionCalculator.tsx 124 | │ ├── BoltBadge.tsx 125 | │ ├── CalculatorButton.tsx 126 | │ ├── Display.tsx 127 | │ ├── History.tsx 128 | │ └── ModeSelector.tsx 129 | ├── hooks/ 130 | │ └── useCalculator.ts 131 | ├── types/ 132 | │ └── calculator.ts 133 | ├── App.tsx 134 | └── main.tsx 135 | ``` 136 | 137 | ## 🧮 **Complete Scientific Calculator Button Test Cases** 138 | 139 | ### **Row 1 - Trigonometric & Logarithmic Functions** 140 | | Button | Test Input | Expected Result | Expression Shown | 141 | |--------|------------|-----------------|------------------| 142 | | **sin** | `30` → `sin` → `=` | `0.5` (DEG mode) | `sin(30)` | 143 | | **sin⁻¹** | `INV` → `0.5` → `sin` → `=` | `30` (DEG mode) | `asin(0.5)` | 144 | | **cos** | `60` → `cos` → `=` | `0.5` (DEG mode) | `cos(60)` | 145 | | **cos⁻¹** | `INV` → `0.5` → `cos` → `=` | `60` (DEG mode) | `acos(0.5)` | 146 | | **tan** | `45` → `tan` → `=` | `1` (DEG mode) | `tan(45)` | 147 | | **tan⁻¹** | `INV` → `1` → `tan` → `=` | `45` (DEG mode) | `atan(1)` | 148 | | **log** | `100` → `log` → `=` | `2` | `log10(100)` | 149 | | **10ˣ** | `INV` → `2` → `log` → `=` | `100` | `10^(2)` | 150 | | **ln** | `e` → `ln` → `=` | `1` | `log(e)` | 151 | | **eˣ** | `INV` → `1` → `ln` → `=` | `2.71828...` | `exp(1)` | 152 | | **x!** | `5` → `x!` → `=` | `120` | `5!` | 153 | 154 | ### **Row 2 - Constants & Powers** 155 | | Button | Test Input | Expected Result | Expression Shown | 156 | |--------|------------|-----------------|------------------| 157 | | **π** | `π` → `=` | `3.14159265359` | `pi` | 158 | | **e** | `e` → `=` | `2.71828182846` | `e` | 159 | | **x²** | `5` → `x²` | `25` (immediate) | - | 160 | | **√x** | `9` → `√x` | `3` (immediate) | - | 161 | | **xʸ** | `2` → `xʸ` → `3` → `=` | `8` | `2^3` | 162 | | **(** | `5` → `(` → `2` → `+` → `3` → `)` → `=` | `25` | `5(2 + 3)` | 163 | 164 | ### **Row 3 - Clear & Basic Operations** 165 | | Button | Test Input | Expected Result | Notes | 166 | |--------|------------|-----------------|-------| 167 | | **AC** | Any calculation → `AC` | `0`, clears all | Resets everything | 168 | | **C** | `123` → `C` | `0` | Clears display only | 169 | | **)** | `(` → `2` → `+` → `3` → `)` | Shows in expression | Closes parentheses | 170 | | **÷** | `10` → `÷` → `2` → `=` | `5` | `10 / 2` | 171 | | **×** | `6` → `×` → `7` → `=` | `42` | `6 * 7` | 172 | | **DEL** | `123` → `DEL` | `12` | Removes last digit | 173 | 174 | ### **Row 4 - Numbers & Operations** 175 | | Button | Test Input | Expected Result | Expression Shown | 176 | |--------|------------|-----------------|------------------| 177 | | **7,8,9** | `789` | `789` | Number input | 178 | | **−** | `10` → `−` → `3` → `=` | `7` | `10 - 3` | 179 | | **%** | `50` → `%` | `0.5` (immediate) | Converts to decimal | 180 | | **1/x** | `4` → `1/x` | `0.25` (immediate) | Reciprocal | 181 | 182 | ### **Row 5 - Numbers & Operations** 183 | | Button | Test Input | Expected Result | Expression Shown | 184 | |--------|------------|-----------------|------------------| 185 | | **4,5,6** | `456` | `456` | Number input | 186 | | **+** | `5` → `+` → `3` → `=` | `8` | `5 + 3` | 187 | | **±** | `5` → `±` | `-5` (immediate) | Sign toggle | 188 | | **EXP** | `1.5` → `EXP` → `10` | `1.5e10` | Scientific notation | 189 | 190 | ### **Row 6 - Numbers & Special Functions** 191 | | Button | Test Input | Expected Result | Expression Shown | 192 | |--------|------------|-----------------|------------------| 193 | | **1,2,3** | `123` | `123` | Number input | 194 | | **=** | Any expression → `=` | Calculated result | Executes calculation | 195 | | **Ans** | Previous result → `Ans` | Last answer | Recalls last result | 196 | | **mod** | `10` → `mod` → `3` → `=` | `1` | `10 mod 3` | 197 | 198 | ### **Row 7 - Zero, Decimal & Advanced** 199 | | Button | Test Input | Expected Result | Expression Shown | 200 | |--------|------------|-----------------|------------------| 201 | | **Rand** | `Rand` | `0.xxxxx` | Random number 0-1 | 202 | | **0** | `0` or `120` | `0` or `1200` | Number input (spans 2 cols) | 203 | | **.** | `5` → `.` → `25` | `5.25` | Decimal point | 204 | | **\|x\|** | `-5` → `\|x\|` | `5` (immediate) | Absolute value | 205 | 206 | ### **Mode Toggles** 207 | | Button | Test | Expected Result | 208 | |--------|------|-----------------| 209 | | **DEG/RAD** | `π` → `sin` → `=` | DEG: `0`, RAD: `0` | 210 | | **INV** | Toggle → functions change | sin↔sin⁻¹, etc. | 211 | 212 | ## 🧪 **Complex Test Cases** 213 | 1. **Nested Functions**: `cos(sin(45))` → DEG mode → `0.99992384661` 214 | 2. **Mixed Operations**: `2^3 + sqrt(16) - log(100)` → `8 + 4 - 2 = 10` 215 | 3. **Parentheses**: `(2 + 3) * (4 - 1)` → `5 * 3 = 15` 216 | 4. **Constants**: `π * e^2` → `≈22.87` 217 | 5. **Pi with Functions**: `sin(cos(π))` → DEG mode → `0.017426180743` 218 | 6. **Scientific Notation**: `1.5e3 + 500` → `2000` 219 | 220 | ### **Additional Complex Function Tests** 221 | | Input Sequence | Expected Result | Notes | 222 | |----------------|-----------------|-------| 223 | | `45` → `sin` → `cos` → `=` | `0.99992384661` | cos(sin(45°)) in DEG mode | 224 | | `45` → `cos` → `sin` → `=` | `0.012341028215` | sin(cos(45°)) in DEG mode | 225 | | `π` → `cos` → `sin` → `=` | `0.017426180743` | sin(cos(π°)) in DEG mode | 226 | | `30` → `cos` → `sin` → `tan` → `=` | `0.0002637963853` | tan(sin(cos(30°))) in DEG mode | 227 | | `π` → `sin` → `cos` → `=` | TBD | cos(sin(π°)) in DEG mode | 228 | 229 | ## 🎯 Key Components 230 | 231 | ### Calculator Hook (`useCalculator.ts`) 232 | Central state management for all calculator operations, including: 233 | - Display state management 234 | - Memory operations 235 | - Calculation history 236 | - Mode switching 237 | - Theme toggling 238 | 239 | ### Calculator Components 240 | Each calculator mode is implemented as a separate component with specialized functionality: 241 | - Modular design for easy maintenance 242 | - Consistent UI patterns across modes 243 | - Optimized performance for complex calculations 244 | 245 | ## 🔧 Available Scripts 246 | 247 | - `npm run dev` - Start development server 248 | - `npm run build` - Build for production 249 | - `npm run preview` - Preview production build 250 | - `npm run lint` - Run ESLint 251 | 252 | ## 🌟 Features in Detail 253 | 254 | ### Memory Functions 255 | - **MC (Memory Clear)**: Clear stored memory 256 | - **MR (Memory Recall)**: Recall stored value 257 | - **M+ (Memory Add)**: Add current display to memory 258 | - **M- (Memory Subtract)**: Subtract current display from memory 259 | 260 | ### Scientific Functions 261 | - Trigonometric: sin, cos, tan (with inverse functions) 262 | - Logarithmic: log (base 10), ln (natural log) 263 | - Power operations: x², x^y, √x 264 | - Constants: π (pi), e (Euler's number) 265 | - Special functions: factorial (!), absolute value 266 | 267 | ### Graph Features 268 | - Multiple function plotting 269 | - Real-time function updates 270 | - Customizable axis ranges 271 | - Function visibility controls 272 | - Color-coded function identification 273 | 274 | ## 🤝 Contributing 275 | 276 | 1. Fork the repository 277 | 2. Create a feature branch (`git checkout -b feature/amazing-feature`) 278 | 3. Commit your changes (`git commit -m 'Add amazing feature'`) 279 | 4. Push to the branch (`git push origin feature/amazing-feature`) 280 | 5. Open a Pull Request 281 | 282 | ## 📄 License 283 | 284 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 285 | 286 | ## 🙏 Acknowledgments 287 | 288 | - Developed by [seehiong](https://github.com/seehiong) 289 | - Built with [Bolt.new](https://bolt.new/) - AI-powered development platform 290 | - Mathematical calculations powered by [Math.js](https://mathjs.org/) 291 | - Charts rendered with [Chart.js](https://www.chartjs.org/) 292 | - Icons provided by [Lucide React](https://lucide.dev/) 293 | - Styling with [Tailwind CSS](https://tailwindcss.com/) 294 | 295 | ## 📞 Support 296 | 297 | If you encounter any issues or have questions, please: 298 | 1. Check the existing [issues on GitHub](https://github.com/seehiong/swift-calc/issues) 299 | 2. Create a [new issue](https://github.com/seehiong/swift-calc/issues/new) with detailed information 300 | 3. Include steps to reproduce any bugs 301 | 302 | --- 303 | -------------------------------------------------------------------------------- /src/components/calculators/FinancialCalculator.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Calculator, DollarSign, TrendingUp, PiggyBank } from 'lucide-react'; 3 | 4 | interface FinancialCalculatorProps { 5 | calculate: (expression: string) => string; 6 | } 7 | 8 | export const FinancialCalculator: React.FC = ({ calculate }) => { 9 | const [activeTab, setActiveTab] = useState<'loan' | 'compound' | 'investment'>('loan'); 10 | 11 | // Loan Calculator State 12 | const [loanPrincipal, setLoanPrincipal] = useState(''); 13 | const [loanRate, setLoanRate] = useState(''); 14 | const [loanTerm, setLoanTerm] = useState(''); 15 | const [loanPayment, setLoanPayment] = useState(''); 16 | 17 | // Compound Interest State 18 | const [compoundPrincipal, setCompoundPrincipal] = useState(''); 19 | const [compoundRate, setCompoundRate] = useState(''); 20 | const [compoundTime, setCompoundTime] = useState(''); 21 | const [compoundFrequency, setCompoundFrequency] = useState('12'); 22 | const [compoundResult, setCompoundResult] = useState(''); 23 | 24 | // Investment State 25 | const [investmentInitial, setInvestmentInitial] = useState(''); 26 | const [investmentMonthly, setInvestmentMonthly] = useState(''); 27 | const [investmentRate, setInvestmentRate] = useState(''); 28 | const [investmentYears, setInvestmentYears] = useState(''); 29 | const [investmentResult, setInvestmentResult] = useState(''); 30 | 31 | const calculateLoanPayment = () => { 32 | const P = parseFloat(loanPrincipal); 33 | const r = parseFloat(loanRate) / 100 / 12; 34 | const n = parseFloat(loanTerm) * 12; 35 | 36 | if (P && r && n) { 37 | const payment = P * (r * Math.pow(1 + r, n)) / (Math.pow(1 + r, n) - 1); 38 | setLoanPayment(payment.toFixed(2)); 39 | } 40 | }; 41 | 42 | const calculateCompoundInterest = () => { 43 | const P = parseFloat(compoundPrincipal); 44 | const r = parseFloat(compoundRate) / 100; 45 | const n = parseFloat(compoundFrequency); 46 | const t = parseFloat(compoundTime); 47 | 48 | if (P && r && n && t) { 49 | const amount = P * Math.pow(1 + r / n, n * t); 50 | setCompoundResult(amount.toFixed(2)); 51 | } 52 | }; 53 | 54 | const calculateInvestment = () => { 55 | const P = parseFloat(investmentInitial) || 0; 56 | const PMT = parseFloat(investmentMonthly) || 0; 57 | const r = parseFloat(investmentRate) / 100 / 12; 58 | const n = parseFloat(investmentYears) * 12; 59 | 60 | if ((P || PMT) && r && n) { 61 | // Future value of initial investment 62 | const FV1 = P * Math.pow(1 + r, n); 63 | 64 | // Future value of monthly payments (annuity) 65 | const FV2 = PMT * ((Math.pow(1 + r, n) - 1) / r); 66 | 67 | const totalFV = FV1 + FV2; 68 | setInvestmentResult(totalFV.toFixed(2)); 69 | } 70 | }; 71 | 72 | const tabs = [ 73 | { id: 'loan' as const, label: 'Loan Calculator', icon: }, 74 | { id: 'compound' as const, label: 'Compound Interest', icon: }, 75 | { id: 'investment' as const, label: 'Investment', icon: }, 76 | ]; 77 | 78 | return ( 79 |
80 | {/* Tab Navigation */} 81 |
82 | {tabs.map((tab) => ( 83 | 95 | ))} 96 |
97 | 98 | {/* Loan Calculator */} 99 | {activeTab === 'loan' && ( 100 |
101 |

102 | 103 | Loan Payment Calculator 104 |

105 |
106 |
107 |
108 | 111 | setLoanPrincipal(e.target.value)} 115 | className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500" 116 | placeholder="250000" 117 | /> 118 |
119 |
120 | 123 | setLoanRate(e.target.value)} 128 | className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500" 129 | placeholder="3.5" 130 | /> 131 |
132 |
133 | 136 | setLoanTerm(e.target.value)} 140 | className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500" 141 | placeholder="30" 142 | /> 143 |
144 | 150 |
151 |
152 |

Monthly Payment

153 |
154 | ${loanPayment || '0.00'} 155 |
156 | {loanPayment && ( 157 |
158 |

Total Interest: ${((parseFloat(loanPayment) * parseFloat(loanTerm) * 12) - parseFloat(loanPrincipal)).toFixed(2)}

159 |

Total Paid: ${(parseFloat(loanPayment) * parseFloat(loanTerm) * 12).toFixed(2)}

160 |
161 | )} 162 |
163 |
164 |
165 | )} 166 | 167 | {/* Compound Interest Calculator */} 168 | {activeTab === 'compound' && ( 169 |
170 |

171 | 172 | Compound Interest Calculator 173 |

174 |
175 |
176 |
177 | 180 | setCompoundPrincipal(e.target.value)} 184 | className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" 185 | placeholder="10000" 186 | /> 187 |
188 |
189 | 192 | setCompoundRate(e.target.value)} 197 | className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" 198 | placeholder="5" 199 | /> 200 |
201 |
202 | 205 | setCompoundTime(e.target.value)} 209 | className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" 210 | placeholder="10" 211 | /> 212 |
213 |
214 | 217 | 228 |
229 | 235 |
236 |
237 |

Final Amount

238 |
239 | ${compoundResult || '0.00'} 240 |
241 | {compoundResult && compoundPrincipal && ( 242 |
243 |

Interest Earned: ${(parseFloat(compoundResult) - parseFloat(compoundPrincipal)).toFixed(2)}

244 |
245 | )} 246 |
247 |
248 |
249 | )} 250 | 251 | {/* Investment Calculator */} 252 | {activeTab === 'investment' && ( 253 |
254 |

255 | 256 | Investment Calculator 257 |

258 |
259 |
260 |
261 | 264 | setInvestmentInitial(e.target.value)} 268 | className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500" 269 | placeholder="5000" 270 | /> 271 |
272 |
273 | 276 | setInvestmentMonthly(e.target.value)} 280 | className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500" 281 | placeholder="500" 282 | /> 283 |
284 |
285 | 288 | setInvestmentRate(e.target.value)} 293 | className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500" 294 | placeholder="7" 295 | /> 296 |
297 |
298 | 301 | setInvestmentYears(e.target.value)} 305 | className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500" 306 | placeholder="20" 307 | /> 308 |
309 | 315 |
316 |
317 |

Future Value

318 |
319 | ${investmentResult || '0.00'} 320 |
321 | {investmentResult && ( 322 |
323 |

Total Contributions: ${((parseFloat(investmentInitial) || 0) + (parseFloat(investmentMonthly) || 0) * parseFloat(investmentYears) * 12).toFixed(2)}

324 |

Investment Gains: ${(parseFloat(investmentResult) - ((parseFloat(investmentInitial) || 0) + (parseFloat(investmentMonthly) || 0) * parseFloat(investmentYears) * 12)).toFixed(2)}

325 |
326 | )} 327 |
328 |
329 |
330 | )} 331 |
332 | ); 333 | }; -------------------------------------------------------------------------------- /src/components/calculators/ScientificCalculator.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { CalculatorButton } from '../CalculatorButton'; 3 | import { Display } from '../Display'; 4 | 5 | interface ScientificCalculatorProps { 6 | display: string; 7 | setDisplay: (value: string) => void; 8 | memory: number; 9 | calculate: (expression: string) => string; 10 | clearDisplay: () => void; 11 | clearAll: () => void; 12 | recallLastResult: () => void; 13 | addToHistory: (expression: string, result: string) => void; 14 | } 15 | 16 | export const ScientificCalculator: React.FC = ({ 17 | display, 18 | setDisplay, 19 | memory, 20 | calculate, 21 | clearDisplay, 22 | clearAll, 23 | recallLastResult, 24 | addToHistory, 25 | }) => { 26 | const [expression, setExpression] = useState(''); 27 | const [displayExpression, setDisplayExpression] = useState(''); 28 | const [openParenCount, setOpenParenCount] = useState(0); 29 | const [isInverse, setIsInverse] = useState(false); 30 | const [angleMode, setAngleMode] = useState<'DEG' | 'RAD'>('DEG'); 31 | const [waitingForOperand, setWaitingForOperand] = useState(false); 32 | 33 | const inputNumber = (num: string) => { 34 | if (waitingForOperand) { 35 | setDisplay(num); 36 | setWaitingForOperand(false); 37 | } else { 38 | if (num === '.' && display.includes('.')) { 39 | return; // Don't add multiple decimal points 40 | } 41 | if (display === '0' && num !== '.') { 42 | setDisplay(num); 43 | } else { 44 | setDisplay(display + num); 45 | } 46 | } 47 | }; 48 | 49 | const inputOperator = (op: string) => { 50 | // Add current display to both expression and display expression 51 | const newExpression = expression + display + ' ' + op + ' '; 52 | const newDisplayExpression = displayExpression + display + ' ' + op + ' '; 53 | 54 | setExpression(newExpression); 55 | setDisplayExpression(newDisplayExpression); 56 | setWaitingForOperand(true); 57 | }; 58 | 59 | const inputOpenParenthesis = () => { 60 | // If there's a number in display and we're not waiting for operand, add multiplication 61 | if (!waitingForOperand && display !== '0' && display !== '') { 62 | const newExpression = expression + display + ' * ('; 63 | const newDisplayExpression = displayExpression + display + ' * ('; 64 | setExpression(newExpression); 65 | setDisplayExpression(newDisplayExpression); 66 | } else { 67 | // Just add the opening parenthesis 68 | setExpression(prev => prev + '('); 69 | setDisplayExpression(prev => prev + '('); 70 | } 71 | 72 | setOpenParenCount(openParenCount + 1); 73 | setDisplay(''); 74 | setWaitingForOperand(false); 75 | }; 76 | 77 | const inputCloseParenthesis = () => { 78 | if (openParenCount === 0) return; // No matching open parenthesis 79 | 80 | // Add current display to expressions, then add closing parenthesis 81 | const currentValue = waitingForOperand ? '' : display; 82 | const newExpression = expression + currentValue + ')'; 83 | const newDisplayExpression = displayExpression + currentValue + ')'; 84 | 85 | setExpression(newExpression); 86 | setDisplayExpression(newDisplayExpression); 87 | setOpenParenCount(openParenCount - 1); 88 | setDisplay(''); 89 | setWaitingForOperand(true); 90 | }; 91 | const inputFunction = (func: string) => { 92 | let functionName = func; 93 | let mathJsFunction = func; 94 | 95 | if (isInverse) { 96 | switch (func) { 97 | case 'sin': 98 | functionName = 'sin⁻¹'; 99 | mathJsFunction = 'asin'; 100 | break; 101 | case 'cos': 102 | functionName = 'cos⁻¹'; 103 | mathJsFunction = 'acos'; 104 | break; 105 | case 'tan': 106 | functionName = 'tan⁻¹'; 107 | mathJsFunction = 'atan'; 108 | break; 109 | case 'log': 110 | functionName = '10^'; 111 | mathJsFunction = '10^'; 112 | break; 113 | case 'ln': 114 | functionName = 'e^'; 115 | mathJsFunction = 'exp'; 116 | break; 117 | } 118 | } 119 | 120 | // Always wrap the current display in the new function 121 | const newDisplay = functionName + '(' + display + ')'; 122 | setDisplay(newDisplay); 123 | 124 | setWaitingForOperand(true); 125 | setIsInverse(false); 126 | }; 127 | 128 | const inputFactorial = () => { 129 | setDisplay(display + '!'); 130 | setWaitingForOperand(true); 131 | }; 132 | 133 | const inputConstant = (constant: string) => { 134 | if (waitingForOperand || display === '0') { 135 | setDisplay(constant); 136 | setWaitingForOperand(false); 137 | } else { 138 | setDisplay(display + constant); 139 | } 140 | }; 141 | 142 | const performCalculation = () => { 143 | let fullExpression = expression + display; 144 | 145 | if (fullExpression.trim() === '') { 146 | fullExpression = display; 147 | } 148 | 149 | console.log('Original expression:', fullExpression); 150 | 151 | // Replace constants first, before other transformations 152 | fullExpression = fullExpression 153 | .replace(/π/g, 'pi') 154 | .replace(/(? { 204 | const currentValue = parseFloat(display); 205 | let result: string; 206 | 207 | try { 208 | switch (func) { 209 | case 'x²': 210 | result = calculate(`(${display})^2`); 211 | break; 212 | case '√': 213 | result = calculate(`sqrt(${display})`); 214 | break; 215 | case '1/x': 216 | result = calculate(`1/(${display})`); 217 | break; 218 | case '%': 219 | result = (currentValue / 100).toString(); 220 | break; 221 | case 'EXP': 222 | // Handle scientific notation (e.g., 1.5e10) 223 | if (!display.includes('e') && !waitingForOperand) { 224 | setDisplay(display + 'e'); 225 | } 226 | return; 227 | case '±': 228 | result = display.startsWith('-') ? display.slice(1) : '-' + display; 229 | break; 230 | case '|x|': 231 | result = Math.abs(currentValue).toString(); 232 | break; 233 | default: 234 | return; 235 | } 236 | 237 | setDisplay(result); 238 | setWaitingForOperand(true); 239 | } catch (error) { 240 | setDisplay('Error'); 241 | } 242 | }; 243 | 244 | const handleClear = () => { 245 | clearDisplay(); 246 | setExpression(''); 247 | setDisplayExpression(''); 248 | setOpenParenCount(0); 249 | setWaitingForOperand(false); 250 | }; 251 | 252 | const handleAllClear = () => { 253 | clearAll(); 254 | setExpression(''); 255 | setDisplayExpression(''); 256 | setOpenParenCount(0); 257 | setWaitingForOperand(false); 258 | setIsInverse(false); 259 | }; 260 | 261 | // Show the display expression with current input 262 | const fullDisplayExpression = displayExpression + (waitingForOperand ? '' : display); 263 | return ( 264 |
265 | 266 | 267 | {/* Mode indicators */} 268 |
269 | 275 | 281 | {openParenCount > 0 && ( 282 |
283 | {openParenCount} open paren{openParenCount > 1 ? 's' : ''} 284 |
285 | )} 286 |
287 | 288 |
289 | {/* Row 1 - Functions */} 290 | inputFunction('sin')} variant="function"> 291 | {isInverse ? 'sin⁻¹' : 'sin'} 292 | 293 | inputFunction('cos')} variant="function"> 294 | {isInverse ? 'cos⁻¹' : 'cos'} 295 | 296 | inputFunction('tan')} variant="function"> 297 | {isInverse ? 'tan⁻¹' : 'tan'} 298 | 299 | inputFunction('log')} variant="function"> 300 | {isInverse ? '10ˣ' : 'log'} 301 | 302 | inputFunction('ln')} variant="function"> 303 | {isInverse ? 'eˣ' : 'ln'} 304 | 305 | x! 306 | 307 | {/* Row 2 - Powers and roots */} 308 | inputConstant('π')} variant="function">π 309 | inputConstant('e')} variant="function">e 310 | handleSpecialFunction('x²')} variant="function">x² 311 | handleSpecialFunction('√')} variant="function">√x 312 | inputOperator('^')} variant="function">xʸ 313 | ( 314 | 315 | {/* Row 3 */} 316 | AC 317 | C 318 | ) 319 | inputOperator('/')} variant="operator">÷ 320 | inputOperator('*')} variant="operator">× 321 | { 322 | const newDisplay = display.slice(0, -1) || '0'; 323 | setDisplay(newDisplay); 324 | if (newDisplay === '0') setWaitingForOperand(false); 325 | }} variant="clear">DEL 326 | 327 | {/* Row 4 */} 328 | inputNumber('7')}>7 329 | inputNumber('8')}>8 330 | inputNumber('9')}>9 331 | inputOperator('-')} variant="operator">− 332 | handleSpecialFunction('%')} variant="operator">% 333 | handleSpecialFunction('1/x')} variant="function">1/x 334 | 335 | {/* Row 5 */} 336 | inputNumber('4')}>4 337 | inputNumber('5')}>5 338 | inputNumber('6')}>6 339 | inputOperator('+')} variant="operator">+ 340 | handleSpecialFunction('±')} variant="operator">± 341 | { 342 | // Handle scientific notation (e.g., 1.5e10) 343 | if (!display.includes('e') && !waitingForOperand) { 344 | setDisplay(display + 'e'); 345 | } 346 | }} variant="function">EXP 347 | 348 | {/* Row 6 */} 349 | inputNumber('1')}>1 350 | inputNumber('2')}>2 351 | inputNumber('3')}>3 352 | = 353 | Ans 354 | inputOperator('mod')} variant="function">mod 355 | { 356 | setDisplay(Math.random().toString()); 357 | setWaitingForOperand(true); 358 | }} variant="function">Rand 359 | 360 | {/* Row 7 */} 361 | inputNumber('0')} className="col-span-2">0 362 | { 363 | if (waitingForOperand) { 364 | setDisplay('0.'); 365 | setWaitingForOperand(false); 366 | } else if (!display.includes('.')) { 367 | setDisplay(display + '.'); 368 | } 369 | }}>. 370 | handleSpecialFunction('|x|')} variant="function">|x| 371 |
372 |
373 | ); 374 | }; --------------------------------------------------------------------------------