Components
Loading preview...
A popup component to confirm before discarding changes. Inspired by Discord, it will shake when the user tries to leave without saving.
npx shadcn@latest add https://21st.dev/r/KarrixLee/unsave-popup"use client"
import type React from "react"
import { Label } from "@/components/ui/label"
import { X, Info } from "lucide-react"
import { Input } from "@/components/ui/input"
import { useState, useCallback, useMemo } from "react"
import { Button } from "@/components/ui/button"
import { UnsavePopup } from "@/components/ui/unsave-popup"
import { cn } from "@/lib/utils"
export function UnsavePopupDemo() {
const [value, setValue] = useState("")
const [showPopup, setShowPopup] = useState(false)
const [shouldBlockNav, setShouldBlockNav] = useState(false)
const [closeForm, setCloseForm] = useState(false)
const [isSaved, setIsSaved] = useState(false)
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value)
setShowPopup(true)
setIsSaved(false)
}, [])
const handleSave = useCallback(async () => {
await new Promise((resolve) => setTimeout(resolve, 1000))
setShowPopup(false)
setIsSaved(true)
}, [])
const handleReset = useCallback(() => {
setValue("")
setShowPopup(false)
setShouldBlockNav(false)
}, [])
const handleCloseFormClick = useCallback(() => {
if (value && !isSaved) {
setShouldBlockNav(true)
setTimeout(() => setShouldBlockNav(false), 100)
return
}
setCloseForm(true)
}, [value, isSaved])
const shouldBlockFn = useCallback(() => shouldBlockNav, [shouldBlockNav])
const formContent = useMemo(
() => (
<div className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor="name" className="font-medium">
Name
</Label>
<Input
placeholder="Type anything to trigger it..."
value={value}
className="text-sm"
onChange={handleInputChange}
/>
</div>
),
[value, handleInputChange],
)
const popupContent = useMemo(
() => (
<>
<Info className="h-4 w-4" /> Try to press the "x" to close it!
</>
),
[],
)
return (
<div className="min-w-[400px]">
{!closeForm && (
<div key="preview" className="flex flex-col h-full items-center justify-center p-2">
<div
className={cn(
"flex items-center justify-center p-4 gap-2 border border-gray-200",
"rounded-lg w-full max-w-[500px] h-[300px] shadow-md relative",
)}
>
{formContent}
<div className="absolute top-1 right-1">
<Button
variant="ghost"
size="icon"
onClick={handleCloseFormClick}
className="rounded-full hover:bg-transparent"
>
<X className="h-4 w-4 text-gray-500" />
</Button>
</div>
</div>
</div>
)}
<UnsavePopup
onSave={handleSave}
onReset={handleReset}
shouldBlockFn={shouldBlockFn}
show={showPopup}
className="w-full"
>
{popupContent}
</UnsavePopup>
</div>
)
}