Ai Citation
smoothui-registry/ai-citationFreeComponent
Inline citation pill that opens an origin-aware source card, anchored to the pill it came from.
Install it
npx shadcn@latest add https://www.smoothui.dev/r/ai-citation.jsonSource
1"use client";23import { cn } from "@/lib/utils";4import { ArrowUpRight, Globe } from "lucide-react";5import { AnimatePresence, motion, useReducedMotion } from "motion/react";6import { type ReactNode, useEffect, useRef, useState } from "react";78const SPRING_DEFAULT = {9 bounce: 0.1,10 duration: 0.25,11 type: "spring" as const,12};13/** Grace period so the pointer can cross the gap between pill and card. */14const CLOSE_DELAY_MS = 120;15const CARD_WIDTH_PX = 288;1617export type AICitationProps = {18 className?: string;19 /** Excerpt or description shown in the card. */20 description?: string;21 /**22 * The source's mark for the card header — a real isotype or an `<img>`.23 *24 * A node rather than a URL so a brand's own SVG can be passed straight in.25 */26 favicon?: ReactNode;27 /** The number or short label shown in the pill. */28 label: string | number;29 title: string;30 url: string;31};3233const hostOf = (url: string): string => {34 try {35 return new URL(url).hostname.replace(/^www\./, "");36 } catch {37 return url;38 }39};4041/**42 * An inline citation that can be peeked at.43 *44 * The card scales up **from the pill** rather than fading in centred: with a45 * transform origin at the bottom of the marker, the growth points back at what46 * was clicked, so the pointer never loses the thread. It opens on hover and on47 * focus, and closes on Escape — a hover-only preview is unreachable by keyboard48 * and unusable on touch.49 */50const AICitation = ({51 className,52 description,53 favicon,54 label,55 title,56 url,57}: AICitationProps) => {58 const shouldReduceMotion = useReducedMotion();59 const [isOpen, setIsOpen] = useState(false);60 const closeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);6162 const open = () => {63 if (closeTimeoutRef.current) {64 clearTimeout(closeTimeoutRef.current);65 closeTimeoutRef.current = null;66 }67 setIsOpen(true);68 };6970 const scheduleClose = () => {71 closeTimeoutRef.current = setTimeout(72 () => setIsOpen(false),73 CLOSE_DELAY_MS74 );75 };7677 useEffect(78 () => () => {79 if (closeTimeoutRef.current) {80 clearTimeout(closeTimeoutRef.current);81 }82 },83 []84 );8586 useEffect(() => {87 if (!isOpen) {88 return;89 }90 const handle = (event: KeyboardEvent) => {91 if (event.key === "Escape") {92 setIsOpen(false);93 }94 };95 window.addEventListener("keydown", handle);96 return () => window.removeEventListener("keydown", handle);97// … truncated
What it pulls in
npm packages
Files it writes
More from SmoothUI Registry
All 178 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Agent Avataragent-avatar | SmoothUI Registry | Components | Free | no deps | |
| Ai Approvalai-approval | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Artifactai-artifact | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Branchai-branch | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Context Meterai-context-meter | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Conversationai-conversation | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Coreai-core | SmoothUI Registry | Components | Free | 1 dep | |
| Ai Diffai-diff | SmoothUI Registry | Components | Free | 2 deps |
