KarrixLee

Unsave Popup

Buttons 55

About

A popup component to confirm before discarding changes. Inspired by Discord, it will shake when the user tries to leave without saving.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Unsave Popup to your project and fits it to your theme.

Code

How Unsave Popup is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"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 &quot;x&quot; 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>
  )
}

Dependencies

More by KarrixLee

View all
  • Information Card

    Information Card

    341

More in Buttons

View all
  • Button Save

    Button Save

    46
  • Button

    Button

    20
  • Eclipse Button

    Eclipse Button

    5
  • Button

    Button

    6
  • Button

    Button

    14
  • Button Group

    Button Group

    37
  • Button

    Button

    32
  • Split Button

    Split Button

    32