Citation
nexus-ui/citationFreeComponent
Inline source references with hover preview and multi-source carousel
Live preview
live · rendered by the registry
Rendered by nexus-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://nexus-ui.dev/r/citation.jsonSource
1"use client";23import * as React from "react";4import { ArrowLeft01Icon, ArrowRight01Icon } from "@hugeicons/core-free-icons";5import { HugeiconsIcon } from "@hugeicons/react";6import { parse as parseDomain } from "tldts";78import {9 Carousel,10 CarouselContent,11 CarouselItem,12 type CarouselApi,13} from "@/components/ui/carousel";14import {15 HoverCard,16 HoverCardContent,17 HoverCardTrigger,18} from "@/components/ui/hover-card";19import { cn } from "@/lib/utils";2021export type CitationSourceInput = {22 url: string;23 title?: React.ReactNode;24 description?: React.ReactNode;25};2627export type ResolvedCitation = {28 url: string;29 title: React.ReactNode | null;30 description: React.ReactNode | null;31 siteName: string;32 faviconSrc: string;33};3435const HTTP_PROTOCOL_RE = /^https?:$/i;36const tryParseUrl = (value: string): URL | null => {37 try {38 return new URL(value);39 } catch {40 return null;41 }42};43const parseCitationUrlOrNull = (urlStr: string): URL | null => {44 const trimmed = urlStr.trim();45 if (!trimmed) return null;46 try {47 return parseCitationUrl(trimmed);48 } catch {49 return null;50 }51};5253export function parseCitationUrl(urlStr: string): URL {54 const trimmed = urlStr.trim();55 const parsed = tryParseUrl(trimmed) ?? tryParseUrl(`https://${trimmed}`);56 if (!parsed || !HTTP_PROTOCOL_RE.test(parsed.protocol)) {57 throw new Error("Citation URL must use http or https");58 }59 return parsed;60}6162function formatSiteToken(token: string): string {63 const lower = token.toLowerCase();64 const isAlpha = /^[a-z]+$/i.test(lower);65 if (!isAlpha) return token;66 if (lower.length <= 3) {67 return lower.toUpperCase();68 }69 return lower.slice(0, 1).toUpperCase() + lower.slice(1);70}7172function titleCaseLabel(label: string): string {73 if (!label) return label;74 return label75 .split("-")76 .filter(Boolean)77 .map(formatSiteToken)78 .join(" ");79}8081export function rootDomainSiteName(url: URL): string {82 const host = url.hostname.replace(/^www\./i, "").toLowerCase();83 const { domainWithoutSuffix, isIp } = parseDomain(host, {84 allowPrivateDomains: true,85 });86 const label = isIp ? host : (domainWithoutSuffix ?? host.split(".")[0] ?? "");8788 return titleCaseLabel(label);89}9091function hasCitationField(v: unknown): boolean {92 return (93 v !== undefined && v !== null && !(typeof v === "string" && v.trim() === "")94 );95}9697export function resolveCitationSource(98 input: CitationSourceInput,99): ResolvedCitation {100// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from nexus-ui
All 15 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Attachmentsattachments | nexus-ui | Components | Free | 4 deps · 2 files | |
| Chain of Thoughtchain-of-thought | nexus-ui | Components | Free | 2 deps · 2 files | |
| Feedback Barfeedback-bar | nexus-ui | Components | Free | 1 dep | |
| Imageimage | nexus-ui | Components | Free | 2 deps | |
| Messagemessage | nexus-ui | Components | Free | 9 deps · 2 files | |
| Model Selectormodel-selector | nexus-ui | Components | Free | 3 deps | |
| Prompt Inputprompt-input | nexus-ui | Components | Free | 1 dep | |
| Questionsquestions | nexus-ui | Components | Free | 2 deps |
