Not affiliated with official shadcn/ui. Visit ui.shadcn.com for official docs.
Unlock this block—Get Pro at 60% offReact Dialog Block Quick Calculator
Quick calculator dialog with basic operations, calculation history, memory functions, and keyboard support
Looking to implement shadcn/ui blocks?
Join our Discord community for help from other developers.
Calculate instantly with a sleek interface. This React calculator dialog provides basic arithmetic operations, scrollable calculation history, memory storage functions (M+, M-, MR, MC), and full keyboard support for rapid entry. Built with shadcn/ui Dialog, Button, ScrollArea, and Badge components using Tailwind CSS, users perform calculations with immediate results and easy access to previous computations. Type numbers, click operators, recall history—perfect for finance apps, shopping carts, data tools, or any Next.js application where quick calculations enhance user productivity without leaving context.
"use client";import { useState, useEffect, useCallback } from "react";import { Calculator, Delete, History, X, Percent, Divide, Plus, Minus, Equal,} from "lucide-react";import { Button } from "@/components/ui/button";import { Badge } from "@/components/ui/badge";import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger,} from "@/components/ui/dialog";import { ScrollArea } from "@/components/ui/scroll-area";import { cn } from "@/lib/utils";type HistoryEntry = { expression: string; result: string;};type Operator = "+" | "-" | "×" | "÷" | null;export const title = "React Dialog Block Quick Calculator";export default function DialogQuickCalculator() { const [open, setOpen] = useState(false); const [display, setDisplay] = useState("0"); const [previousValue, setPreviousValue] = useState<number | null>(null); const [operator, setOperator] = useState<Operator>(null); const [waitingForOperand, setWaitingForOperand] = useState(false); const [memory, setMemory] = useState<number>(0); const [hasMemory, setHasMemory] = useState(false); const [history, setHistory] = useState<HistoryEntry[]>([]); const [showHistory, setShowHistory] = useState(false); const [expression, setExpression] = useState(""); const formatNumber = (num: number): string => { if (Math.abs(num) >= 1e10 || (Math.abs(num) < 1e-6 && num !== 0)) { return num.toExponential(6); } const formatted = num.toPrecision(12); return parseFloat(formatted).toString(); }; const inputDigit = useCallback((digit: string) => { if (waitingForOperand) { setDisplay(digit); setWaitingForOperand(false); } else { setDisplay(display === "0" ? digit : display + digit); } }, [display, waitingForOperand]); const inputDecimal = useCallback(() => { if (waitingForOperand) { setDisplay("0."); setWaitingForOperand(false); return; } if (!display.includes(".")) { setDisplay(display + "."); } }, [display, waitingForOperand]); const clearAll = useCallback(() => { setDisplay("0"); setPreviousValue(null); setOperator(null); setWaitingForOperand(false); setExpression(""); }, []); const clearEntry = useCallback(() => { setDisplay("0"); }, []); const backspace = useCallback(() => { if (display.length === 1 || (display.length === 2 && display.startsWith("-"))) { setDisplay("0"); } else { setDisplay(display.slice(0, -1)); } }, [display]); const toggleSign = useCallback(() => { const value = parseFloat(display); setDisplay(formatNumber(-value)); }, [display]); const inputPercent = useCallback(() => { const value = parseFloat(display); setDisplay(formatNumber(value / 100)); }, [display]); const calculate = useCallback((leftOperand: number, rightOperand: number, op: Operator): number => { switch (op) { case "+": return leftOperand + rightOperand; case "-": return leftOperand - rightOperand; case "×": return leftOperand * rightOperand; case "÷": return rightOperand !== 0 ? leftOperand / rightOperand : NaN; default: return rightOperand; } }, []); const performOperation = useCallback((nextOperator: Operator) => { const inputValue = parseFloat(display); if (previousValue === null) { setPreviousValue(inputValue); setExpression(`${formatNumber(inputValue)} ${nextOperator}`); } else if (operator) { const result = calculate(previousValue, inputValue, operator); const newExpression = `${expression} ${formatNumber(inputValue)}`; if (nextOperator) { setExpression(`${formatNumber(result)} ${nextOperator}`); } else { setHistory((prev) => [ { expression: newExpression, result: formatNumber(result) }, ...prev.slice(0, 19), ]); setExpression(""); } setDisplay(formatNumber(result)); setPreviousValue(nextOperator ? result : null); } setWaitingForOperand(true); setOperator(nextOperator); }, [display, previousValue, operator, expression, calculate]); const equals = useCallback(() => { if (operator && previousValue !== null) { performOperation(null); } }, [operator, previousValue, performOperation]); const memoryAdd = useCallback(() => { setMemory((prev) => prev + parseFloat(display)); setHasMemory(true); }, [display]); const memorySubtract = useCallback(() => { setMemory((prev) => prev - parseFloat(display)); setHasMemory(true); }, [display]); const memoryRecall = useCallback(() => { setDisplay(formatNumber(memory)); setWaitingForOperand(true); }, [memory]); const memoryClear = useCallback(() => { setMemory(0); setHasMemory(false); }, []); const useHistoryResult = (result: string) => { setDisplay(result); setWaitingForOperand(true); setShowHistory(false); }; useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (!open) return; if (e.key >= "0" && e.key <= "9") { e.preventDefault(); inputDigit(e.key); } else if (e.key === ".") { e.preventDefault(); inputDecimal(); } else if (e.key === "+" || e.key === "-") { e.preventDefault(); performOperation(e.key as Operator); } else if (e.key === "*") { e.preventDefault(); performOperation("×"); } else if (e.key === "/") { e.preventDefault(); performOperation("÷"); } else if (e.key === "Enter" || e.key === "=") { e.preventDefault(); equals(); } else if (e.key === "Escape") { e.preventDefault(); clearAll(); } else if (e.key === "Backspace") { e.preventDefault(); backspace(); } else if (e.key === "%") { e.preventDefault(); inputPercent(); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [open, inputDigit, inputDecimal, performOperation, equals, clearAll, backspace, inputPercent]); const CalcButton = ({ children, onClick, variant = "outline", className, }: { children: React.ReactNode; onClick: () => void; variant?: "default" | "outline" | "secondary" | "ghost"; className?: string; }) => ( <Button variant={variant} className={cn("h-12 text-lg font-medium", className)} onClick={onClick} > {children} </Button> ); return ( <Dialog open={open} onOpenChange={setOpen}> <div className="flex min-h-[350px] items-center justify-center"> <DialogTrigger asChild> <Button variant="outline"> <Calculator className="mr-2 h-4 w-4" /> Calculator </Button> </DialogTrigger> </div> <DialogContent className="sm:max-w-sm"> <DialogHeader> <DialogTitle className="flex items-center justify-between"> <div className="flex items-center gap-2"> <Calculator className="h-5 w-5" /> Calculator </div> <div className="flex items-center gap-1"> {hasMemory && ( <Badge variant="secondary" className="text-xs"> M </Badge> )} <Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setShowHistory(!showHistory)} > <History className="h-4 w-4" /> </Button> </div> </DialogTitle> <DialogDescription className="sr-only"> Perform quick calculations </DialogDescription> </DialogHeader> {showHistory ? ( <div className="space-y-2 py-2"> <div className="flex items-center justify-between"> <span className="text-sm font-medium">History</span> {history.length > 0 && ( <Button variant="ghost" size="sm" onClick={() => setHistory([])} className="h-7 text-xs" > Clear </Button> )} </div> <ScrollArea className="h-[300px]"> {history.length === 0 ? ( <div className="flex flex-col items-center justify-center py-8 text-center"> <History className="h-8 w-8 text-muted-foreground mb-2" /> <p className="text-sm text-muted-foreground">No history yet</p> </div> ) : ( <div className="space-y-2"> {history.map((entry, index) => ( <button key={index} onClick={() => useHistoryResult(entry.result)} className="w-full rounded-lg border p-2 text-left hover:bg-muted transition-colors" > <p className="text-xs text-muted-foreground truncate"> {entry.expression} </p> <p className="text-lg font-mono font-semibold"> = {entry.result} </p> </button> ))} </div> )} </ScrollArea> <Button variant="outline" className="w-full" onClick={() => setShowHistory(false)} > Back to Calculator </Button> </div> ) : ( <div className="space-y-2 py-2"> <div className="rounded-lg border bg-muted/30 p-3 text-right"> {expression && ( <p className="text-xs text-muted-foreground mb-1 truncate"> {expression} </p> )} <p className="text-3xl font-mono font-bold tabular-nums truncate"> {display} </p> </div> <div className="grid grid-cols-4 gap-1"> <CalcButton onClick={memoryClear} variant="ghost" className="text-sm"> MC </CalcButton> <CalcButton onClick={memoryRecall} variant="ghost" className="text-sm"> MR </CalcButton> <CalcButton onClick={memoryAdd} variant="ghost" className="text-sm"> M+ </CalcButton> <CalcButton onClick={memorySubtract} variant="ghost" className="text-sm"> M- </CalcButton> </div> <div className="grid grid-cols-4 gap-1"> <CalcButton onClick={clearAll} variant="secondary"> AC </CalcButton> <CalcButton onClick={toggleSign} variant="secondary"> ± </CalcButton> <CalcButton onClick={inputPercent} variant="secondary"> <Percent className="h-4 w-4" /> </CalcButton> <CalcButton onClick={() => performOperation("÷")} variant="default"> <Divide className="h-4 w-4" /> </CalcButton> <CalcButton onClick={() => inputDigit("7")}>7</CalcButton> <CalcButton onClick={() => inputDigit("8")}>8</CalcButton> <CalcButton onClick={() => inputDigit("9")}>9</CalcButton> <CalcButton onClick={() => performOperation("×")} variant="default"> <X className="h-4 w-4" /> </CalcButton> <CalcButton onClick={() => inputDigit("4")}>4</CalcButton> <CalcButton onClick={() => inputDigit("5")}>5</CalcButton> <CalcButton onClick={() => inputDigit("6")}>6</CalcButton> <CalcButton onClick={() => performOperation("-")} variant="default"> <Minus className="h-4 w-4" /> </CalcButton> <CalcButton onClick={() => inputDigit("1")}>1</CalcButton> <CalcButton onClick={() => inputDigit("2")}>2</CalcButton> <CalcButton onClick={() => inputDigit("3")}>3</CalcButton> <CalcButton onClick={() => performOperation("+")} variant="default"> <Plus className="h-4 w-4" /> </CalcButton> <CalcButton onClick={() => inputDigit("0")} className="col-span-2"> 0 </CalcButton> <CalcButton onClick={inputDecimal}>.</CalcButton> <CalcButton onClick={equals} variant="default"> <Equal className="h-4 w-4" /> </CalcButton> </div> <p className="text-xs text-center text-muted-foreground"> Keyboard supported </p> </div> )} </DialogContent> </Dialog> );}Installation
npx shadcn@latest add https://www.shadcn.io/registry/dialog-quick-calculator.jsonnpx shadcn@latest add https://www.shadcn.io/registry/dialog-quick-calculator.jsonpnpm dlx shadcn@latest add https://www.shadcn.io/registry/dialog-quick-calculator.jsonbunx shadcn@latest add https://www.shadcn.io/registry/dialog-quick-calculator.jsonRelated blocks you will also like
React Dialog Block Unit Converter
Value conversion
React Dialog Block Budget Allocator
Financial calculations
React Dialog Block Quick Note
Note taking
React Dialog Block Keyboard Shortcuts
Keyboard navigation
React Dialog Block Password Generator
Number generation
React Dialog Block Countdown Timer
Number display