KarrixLee

Unsave Popup

Buttons 56

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

    345

More in Buttons

View all
  • Button

    Button

    5
  • Texture Button

    Texture Button

    97
  • Button Group

    Button Group

  • Eclipse Button

    Eclipse Button

    39
  • Button

    Button

    4
  • HeroUI Button Group

    HeroUI Button Group

  • Base Button

    Base Button

    4
  • App Store Button

    App Store Button

    458