Feedback Popover
ericts-ui/feedback-popoverFreeComponent
A Motion-powered feedback popover that expands from a trigger, supports keyboard submission, and confirms receipt inline.
Live preview
live · rendered by the registry
Rendered by ericts-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.ericts.com/r/feedback-popover.jsonSource
1"use client";23import * as React from "react";4import { CheckCircle2 } from "lucide-react";5import {6 AnimatePresence,7 LayoutGroup,8 motion,9 useReducedMotion,10} from "motion/react";1112import { Button, buttonVariants } from "@/components/ui/button";13import { cn } from "@/lib/utils";1415type FeedbackState = "idle" | "loading" | "success";1617export type FeedbackPopoverProps = Omit<18 React.ComponentPropsWithoutRef<"div">,19 "children" | "onSubmit"20> & {21 open?: boolean;22 defaultOpen?: boolean;23 onOpenChange?: (open: boolean) => void;24 onSubmit?: (feedback: string) => void | Promise<void>;25 triggerLabel?: React.ReactNode;26 textareaLabel?: string;27 placeholder?: string;28 submitLabel?: React.ReactNode;29 successTitle?: React.ReactNode;30 successDescription?: React.ReactNode;31 loadingAnnouncement?: string;32 loadingDuration?: number;33 successDuration?: number;34};3536function Spinner({ className }: { className?: string }) {37 return (38 <span39 aria-hidden="true"40 className={cn(41 "size-3.5 animate-spin rounded-full border-2 border-current border-t-transparent opacity-70",42 className,43 )}44 />45 );46}4748function wait(duration: number) {49 return new Promise((resolve) => {50 window.setTimeout(resolve, duration);51 });52}5354export function FeedbackPopover({55 open,56 defaultOpen = false,57 onOpenChange,58 onSubmit,59 triggerLabel = "Feedback",60 textareaLabel = "Feedback",61 placeholder = "Feedback",62 submitLabel = "Send feedback",63 successTitle = "Feedback received!",64 successDescription = "Thanks for helping us improve.",65 loadingAnnouncement = "Sending feedback",66 loadingDuration = 1500,67 successDuration = 1800,68 className,69 ...props70}: FeedbackPopoverProps) {71 const reactId = React.useId();72 const titleId = `${reactId}-title`;73 const descriptionId = `${reactId}-description`;74 const popoverRef = React.useRef<HTMLDivElement>(null);75 const timers = React.useRef<number[]>([]);76 const isMountedRef = React.useRef(true);77 const shouldReduceMotion = useReducedMotion();78 const isControlled = open !== undefined;79 const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);80 const [feedback, setFeedback] = React.useState("");81 const [formState, setFormState] = React.useState<FeedbackState>("idle");82 const isOpen = isControlled ? open : uncontrolledOpen;83 const trimmedFeedback = feedback.trim();8485 const clearTimers = React.useCallback(() => {86 timers.current.forEach(window.clearTimeout);87// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from ericts-ui
All 38 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Adaptive Draweradaptive-drawer | ericts-ui | Components | Free | 2 deps | |
| Check Animationcheck-animation | ericts-ui | Components | Free | no deps · 2 files | |
| Context Cursorcontext-cursor | ericts-ui | Components | Free | 1 dep | |
| Copy Buttoncopy-button | ericts-ui | Components | Free | 2 deps | |
| Expandable Dialogexpandable-modal | ericts-ui | Components | Free | 1 dep | |
| Expandable Tabsexpandable-tabs | ericts-ui | Components | Free | 1 dep | |
| Expandable Toolbarexpandable-toolbar | ericts-ui | Components | Free | 1 dep | |
| Expanding Panelexpanding-panel | ericts-ui | Components | Free | 2 deps |
