"use client" ;
import { useState, useMemo } from "react" ;
import { Receipt, Plus, Minus, Check, Copy } from "lucide-react" ;
import { Button } from "@/components/ui/button" ;
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog" ;
import { Input } from "@/components/ui/input" ;
import { Label } from "@/components/ui/label" ;
import { Separator } from "@/components/ui/separator" ;
import { cn } from "@/lib/utils" ;
const tipPresets = [ 15 , 18 , 20 , 25 ];
export const title = "React Dialog Block Tip Calculator" ;
export default function DialogTipCalculator () {
const [ open , setOpen ] = useState ( false );
const [ billAmount , setBillAmount ] = useState ( "85.50" );
const [ tipPercentage , setTipPercentage ] = useState ( 20 );
const [ splitCount , setSplitCount ] = useState ( 1 );
const [ copied , setCopied ] = useState ( false );
const bill = parseFloat (billAmount) || 0 ;
const calculations = useMemo (() => {
const tipAmount = bill * (tipPercentage / 100 );
const total = bill + tipAmount;
const perPerson = total / splitCount;
return {
tipAmount,
total,
perPerson,
};
}, [bill, tipPercentage, splitCount]);
const formatCurrency = ( amount : number ) => {
return new Intl. NumberFormat ( "en-US" , {
style: "currency" ,
currency: "USD" ,
}). format (amount);
};
const handleSplitChange = ( delta : number ) => {
setSplitCount (( prev ) => Math. max ( 1 , Math. min ( 20 , prev + delta)));
};
const handleCopy = () => {
const summary = `Bill: ${ formatCurrency ( bill ) } \n Tip (${ tipPercentage }%): ${ formatCurrency ( calculations . tipAmount ) } \n Total: ${ formatCurrency ( calculations . total ) }${ splitCount > 1 ? ` \n Per person (${ splitCount }): ${ formatCurrency ( calculations . perPerson ) }` : ""}` ;
navigator.clipboard. writeText (summary);
setCopied ( true );
setTimeout (() => setCopied ( false ), 1500 );
};
return (
< Dialog open = {open} onOpenChange = {setOpen}>
< div className = "flex min-h-[350px] items-center justify-center" >
< DialogTrigger asChild >
< Button variant = "outline" >
< Receipt className = "mr-2 h-4 w-4" />
Tip Calculator
</ Button >
</ DialogTrigger >
</ div >
< DialogContent style = {{ maxWidth: "384px" }}>
< DialogHeader >
< DialogTitle >Tip Calculator</ DialogTitle >
< DialogDescription >
Calculate tip and split the bill.
</ DialogDescription >
</ DialogHeader >
< div className = "space-y-4 py-4" >
{ /* Bill Amount */ }
< div className = "space-y-2" >
< Label htmlFor = "bill" className = "text-sm" >Bill Amount</ Label >
< div className = "relative" >
< span className = "absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" >$</ span >
< Input
id = "bill"
type = "number"
min = "0"
step = "0.01"
value = {billAmount}
onChange = {( e ) => setBillAmount (e.target.value)}
className = "pl-7 font-mono"
placeholder = "0.00"
/>
</ div >
</ div >
{ /* Tip Percentage */ }
< div className = "space-y-2" >
< Label className = "text-sm" >Tip</ Label >
< div className = "grid grid-cols-4 gap-2" >
{tipPresets. map (( preset ) => (
< Button
key = {preset}
variant = {tipPercentage === preset ? "default" : "outline" }
size = "sm"
onClick = {() => setTipPercentage (preset)}
>
{preset}%
</ Button >
))}
</ div >
</ div >
{ /* Split */ }
< div className = "flex items-center justify-between" >
< Label className = "text-sm" >Split</ Label >
< div className = "flex items-center gap-2" >
< Button
variant = "outline"
size = "icon"
className = "h-8 w-8"
onClick = {() => handleSplitChange ( - 1 )}
disabled = {splitCount <= 1 }
>
< Minus className = "h-4 w-4" />
</ Button >
< span className = "w-6 text-center font-mono" >{splitCount}</ span >
< Button
variant = "outline"
size = "icon"
className = "h-8 w-8"
onClick = {() => handleSplitChange ( 1 )}
disabled = {splitCount >= 20 }
>
< Plus className = "h-4 w-4" />
</ Button >
</ div >
</ div >
< Separator />
{ /* Results */ }
< div className = "space-y-2 text-sm" >
< div className = "flex justify-between" >
< span className = "text-muted-foreground" >Tip ({tipPercentage}%)</ span >
< span className = "font-mono" >{ formatCurrency (calculations.tipAmount)}</ span >
</ div >
< div className = "flex justify-between font-medium" >
< span >Total</ span >
< span className = "font-mono" >{ formatCurrency (calculations.total)}</ span >
</ div >
{splitCount > 1 && (
< div className = "flex justify-between pt-2 border-t text-primary font-medium" >
< span >Per person</ span >
< span className = "font-mono" >{ formatCurrency (calculations.perPerson)}</ span >
</ div >
)}
</ div >
</ div >
< DialogFooter >
< Button variant = "outline" size = "sm" onClick = {handleCopy}>
{copied ? < Check className = "mr-2 h-4 w-4" /> : < Copy className = "mr-2 h-4 w-4" />}
{copied ? "Copied" : "Copy" }
</ Button >
< Button onClick = {() => setOpen ( false )}>Done</ Button >
</ DialogFooter >
</ DialogContent >
</ Dialog >
);
}