Feedback Widget
beui/feedback-widgetFreeBlock
Corner trigger that morphs open into a feedback popup with message entry and animated sending, success and retry states.
Install it
npx shadcn@latest add https://beui.dev/r/feedback-widget.jsonSource
1"use client";2// beui.dev/components/blocks/feedback-widget34import { AlertCircle, MessageSquare, X } from "lucide-react";5import { AnimatePresence, motion, useReducedMotion } from "motion/react";6import {7 type ReactNode,8 useCallback,9 useEffect,10 useRef,11 useState,12} from "react";13import { Button, StatefulButton } from "@/components/motion/button";14import { EASE_OUT } from "@/lib/ease";15import { cn } from "@/lib/utils";1617type Status = "idle" | "open" | "sending" | "sent" | "error";1819const SUCCESS_DURATION_MS = 1600;2021// The trigger grows into the panel with a slight overshoot on open, then uses22// a calmer curve on close so dismissing feels faster and less prominent.23const MORPH_OPEN_EASE = [0.34, 1.25, 0.64, 1] as const;24const MORPH_CLOSE_EASE = [0.22, 1, 0.36, 1] as const;25const MORPH_OPEN_DURATION = 0.4;26const MORPH_CLOSE_DURATION = 0.28;27const MORPH_FADE_DURATION = 0.22;28const MORPH_SLIDE = 40;29const MORPH_SCALE = 0.97;30const MORPH_BLUR = "blur(2px)";3132// Celebration sprinkles that burst from the success icon.33const SPRINKLES = Array.from({ length: 8 }, (_, i) => {34 const angle = (i / 8) * Math.PI * 2;35 return {36 x: Math.cos(angle) * 26,37 y: Math.sin(angle) * 26,38 color: i % 2 === 0 ? "var(--color-success)" : "var(--accent)",39 };40});4142export interface FeedbackData {43 message: string;44}4546export interface FeedbackWidgetProps {47 /** Called on submit. May be async; the button shows a sending state until it resolves. */48 onSubmit?: (data: FeedbackData) => void | Promise<void>;49 position?: "bottom-right" | "bottom-left";50 title?: string;51 placeholder?: string;52 icon?: ReactNode;53 className?: string;54}5556export function FeedbackWidget({57 onSubmit,58 position = "bottom-right",59 title = "Help us improve",60 placeholder = "Share an idea or report a bug",61 icon,62 className,63}: FeedbackWidgetProps) {64 const reduce = useReducedMotion();65 const rootRef = useRef<HTMLDivElement>(null);66 const textareaRef = useRef<HTMLTextAreaElement>(null);67 const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);6869 const [status, setStatus] = useState<Status>("idle");70 const [message, setMessage] = useState("");7172 const open = status !== "idle";73 const busy = status === "sending";7475 const clearCloseTimer = useCallback(() => {76 if (closeTimerRef.current === null) return;77 clearTimeout(closeTimerRef.current);78 closeTimerRef.current = null;79 }, []);8081 const close = useCallback(() => {82 clearCloseTimer();83// … truncated
What it pulls in
npm packages
Files it writes
More from beui
All 104 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| File Upload Attachment Uploadattachment-upload | beui | Blocks | Free | 4 deps · 5 files | |
| Availability Scheduleravailability-scheduler | beui | Blocks | Free | 4 deps · 15 files | |
| Bloom Menubloom-menu | beui | Blocks | Free | 4 deps · 3 files | |
| Command Palettecommand-palette | beui | Blocks | Free | 4 deps · 3 files | |
| Dynamic Islanddynamic-island | beui | Blocks | Free | 3 deps · 3 files | |
| Expandable Action Barexpandable-action-bar | beui | Blocks | Free | 3 deps · 2 files | |
| Expandable Tabsexpandable-tabs | beui | Blocks | Free | 3 deps · 3 files | |
| File Upload Upload Queuefile-upload | beui | Blocks | Free | 4 deps · 3 files |
