Inline Citation
Inline source chip with a hover card that pages through the sources.
Nvidia beat revenue estimates for the quarterreuters.com +2 and raised guidance.
Nvidia beat revenue estimates for the quarterreuters.com +2 and raised guidance.
Nvidia beat revenue estimates for the quarter reuters.com +2 and raised guidance.
import { InlineCitation, InlineCitationCard, InlineCitationCardBody, InlineCitationCardTrigger, InlineCitationCarousel, InlineCitationCarouselContent, InlineCitationCarouselHeader, InlineCitationCarouselIndex, InlineCitationCarouselItem, InlineCitationCarouselNext, InlineCitationCarouselPrev, InlineCitationQuote, InlineCitationSource, InlineCitationText,} from "@/components/ai/inline-citation";
const sources = [ { title: "Nvidia Q3 results beat estimates", url: "https://reuters.com/technology/nvidia-q3", quote: "Revenue rose 94% from a year earlier…", }, { title: "Form 10-Q, quarterly report", url: "https://sec.gov/cgi-bin/browse-edgar", quote: "Data center revenue was a record for the quarter.", }, { title: "NVDA quote and guidance", url: "https://nasdaq.com/market-activity/stocks/nvda", quote: "The company raised its outlook for the next quarter.", },];
export default function Demo() { return ( <p className="max-w-md text-sm leading-[1.7]"> <InlineCitation> <InlineCitationText> Nvidia beat revenue estimates for the quarter </InlineCitationText> <InlineCitationCard> <InlineCitationCardTrigger sources={sources.map((s) => s.url)} /> <InlineCitationCardBody> <InlineCitationCarousel> <InlineCitationCarouselHeader> <InlineCitationCarouselPrev /> <InlineCitationCarouselNext /> <InlineCitationCarouselIndex /> </InlineCitationCarouselHeader> <InlineCitationCarouselContent> {sources.map((s) => ( <InlineCitationCarouselItem key={s.url}> <InlineCitationSource title={s.title} url={s.url} /> <InlineCitationQuote>{s.quote}</InlineCitationQuote> </InlineCitationCarouselItem> ))} </InlineCitationCarouselContent> </InlineCitationCarousel> </InlineCitationCardBody> </InlineCitationCard> </InlineCitation>{" "} and raised guidance. </p> );}<script setup lang="ts">import { InlineCitation, InlineCitationCard, InlineCitationCardBody, InlineCitationCardTrigger, InlineCitationCarousel, InlineCitationCarouselContent, InlineCitationCarouselHeader, InlineCitationCarouselIndex, InlineCitationCarouselItem, InlineCitationCarouselNext, InlineCitationCarouselPrev, InlineCitationQuote, InlineCitationSource, InlineCitationText,} from "@/components/ai/inline-citation";
const sources = [ { title: "Nvidia Q3 results beat estimates", url: "https://reuters.com/technology/nvidia-q3", quote: "Revenue rose 94% from a year earlier…", }, { title: "Form 10-Q, quarterly report", url: "https://sec.gov/cgi-bin/browse-edgar", quote: "Data center revenue was a record for the quarter.", }, { title: "NVDA quote and guidance", url: "https://nasdaq.com/market-activity/stocks/nvda", quote: "The company raised its outlook for the next quarter.", },];</script>
<template> <p class="max-w-md text-sm leading-[1.7]"> <InlineCitation> <InlineCitationText>Nvidia beat revenue estimates for the quarter</InlineCitationText> <InlineCitationCard> <InlineCitationCardTrigger :sources="sources.map((s) => s.url)" /> <InlineCitationCardBody> <InlineCitationCarousel> <InlineCitationCarouselHeader> <InlineCitationCarouselPrev /> <InlineCitationCarouselNext /> <InlineCitationCarouselIndex /> </InlineCitationCarouselHeader> <InlineCitationCarouselContent> <InlineCitationCarouselItem v-for="s in sources" :key="s.url"> <InlineCitationSource :title="s.title" :url="s.url" /> <InlineCitationQuote>{{ s.quote }}</InlineCitationQuote> </InlineCitationCarouselItem> </InlineCitationCarouselContent> </InlineCitationCarousel> </InlineCitationCardBody> </InlineCitationCard> </InlineCitation> and raised guidance. </p></template><script lang="ts"> import { InlineCitation, InlineCitationCard, InlineCitationCardBody, InlineCitationCardTrigger, InlineCitationCarousel, InlineCitationCarouselContent, InlineCitationCarouselHeader, InlineCitationCarouselIndex, InlineCitationCarouselItem, InlineCitationCarouselNext, InlineCitationCarouselPrev, InlineCitationQuote, InlineCitationSource, InlineCitationText, } from "$lib/components/ai/inline-citation";
const sources = [ { title: "Nvidia Q3 results beat estimates", url: "https://reuters.com/technology/nvidia-q3", quote: "Revenue rose 94% from a year earlier…", }, { title: "Form 10-Q, quarterly report", url: "https://sec.gov/cgi-bin/browse-edgar", quote: "Data center revenue was a record for the quarter.", }, { title: "NVDA quote and guidance", url: "https://nasdaq.com/market-activity/stocks/nvda", quote: "The company raised its outlook for the next quarter.", }, ];</script>
<p class="max-w-md text-sm leading-[1.7]"> <InlineCitation> <InlineCitationText>Nvidia beat revenue estimates for the quarter</InlineCitationText> <InlineCitationCard> <InlineCitationCardTrigger sources={sources.map((s) => s.url)} /> <InlineCitationCardBody> <InlineCitationCarousel> <InlineCitationCarouselHeader> <InlineCitationCarouselPrev /> <InlineCitationCarouselNext /> <InlineCitationCarouselIndex /> </InlineCitationCarouselHeader> <InlineCitationCarouselContent> {#each sources as s (s.url)} <InlineCitationCarouselItem> <InlineCitationSource title={s.title} url={s.url} /> <InlineCitationQuote>{s.quote}</InlineCitationQuote> </InlineCitationCarouselItem> {/each} </InlineCitationCarouselContent> </InlineCitationCarousel> </InlineCitationCardBody> </InlineCitationCard> </InlineCitation> and raised guidance.</p>Built on badge, button, carousel and hover-card.
Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/ai-inline-citationpnpm dlx shadcn@latest add @edmi-ui/ai-inline-citationyarn dlx shadcn@latest add @edmi-ui/ai-inline-citationbunx shadcn@latest add @edmi-ui/ai-inline-citationPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
badge,button,carousel,hover-card.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/ai-inline-citationpnpm dlx shadcn-vue@latest add @edmi-ui/ai-inline-citationyarn dlx shadcn-vue@latest add @edmi-ui/ai-inline-citationbunx shadcn-vue@latest add @edmi-ui/ai-inline-citationPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @lucide/vue reka-uiTerminal window pnpm add @lucide/vue reka-uiTerminal window yarn add @lucide/vue reka-uiTerminal window bun add @lucide/vue reka-uiAdd the Edmi components this one builds on:
badge,button,carousel,hover-card.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-inline-citation.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-inline-citation.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-inline-citation.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-inline-citation.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
badge,button,carousel,hover-card.Copy the code from Component source into your project and update the import paths to match your aliases.
import { InlineCitation, InlineCitationCard, InlineCitationCardBody, InlineCitationCardTrigger, InlineCitationCarousel, InlineCitationCarouselContent, InlineCitationCarouselHeader, InlineCitationCarouselIndex, InlineCitationCarouselNext, InlineCitationCarouselPrev, InlineCitationText,} from "@/components/ai/inline-citation";
<p className="max-w-md text-sm leading-[1.7]"> <InlineCitation> <InlineCitationText> Nvidia beat revenue estimates for the quarter </InlineCitationText> <InlineCitationCard> <InlineCitationCardTrigger /> <InlineCitationCardBody> <InlineCitationCarousel> <InlineCitationCarouselHeader> <InlineCitationCarouselPrev /> <InlineCitationCarouselNext /> <InlineCitationCarouselIndex /> </InlineCitationCarouselHeader> <InlineCitationCarouselContent> </InlineCitationCarouselContent> </InlineCitationCarousel> </InlineCitationCardBody> </InlineCitationCard> </InlineCitation>{" "} and raised guidance.</p><script setup lang="ts">import { InlineCitation, InlineCitationCard, InlineCitationCardBody, InlineCitationCardTrigger, InlineCitationCarousel, InlineCitationCarouselContent, InlineCitationCarouselHeader, InlineCitationCarouselIndex, InlineCitationCarouselItem, InlineCitationCarouselNext, InlineCitationCarouselPrev, InlineCitationQuote, InlineCitationSource, InlineCitationText,} from "@/components/ai/inline-citation";</script>
<template> <p class="max-w-md text-sm leading-[1.7]"> <InlineCitation> <InlineCitationText>Nvidia beat revenue estimates for the quarter</InlineCitationText> <InlineCitationCard> <InlineCitationCardTrigger /> <InlineCitationCardBody> <InlineCitationCarousel> <InlineCitationCarouselHeader> <InlineCitationCarouselPrev /> <InlineCitationCarouselNext /> <InlineCitationCarouselIndex /> </InlineCitationCarouselHeader> <InlineCitationCarouselContent> <InlineCitationCarouselItem :key="s.url"> <InlineCitationSource :title="s.title" :url="s.url" /> <InlineCitationQuote>{{ s.quote }}</InlineCitationQuote> </InlineCitationCarouselItem> </InlineCitationCarouselContent> </InlineCitationCarousel> </InlineCitationCardBody> </InlineCitationCard> </InlineCitation> and raised guidance. </p></template><script lang="ts"> import { InlineCitation, InlineCitationCard, InlineCitationCardBody, InlineCitationCardTrigger, InlineCitationCarousel, InlineCitationCarouselContent, InlineCitationCarouselHeader, InlineCitationCarouselIndex, InlineCitationCarouselItem, InlineCitationCarouselNext, InlineCitationCarouselPrev, InlineCitationQuote, InlineCitationSource, InlineCitationText, } from "$lib/components/ai/inline-citation";</script>
<p class="max-w-md text-sm leading-[1.7]"> <InlineCitation> <InlineCitationText>Nvidia beat revenue estimates for the quarter</InlineCitationText> <InlineCitationCard> <InlineCitationCardTrigger /> <InlineCitationCardBody> <InlineCitationCarousel> <InlineCitationCarouselHeader> <InlineCitationCarouselPrev /> <InlineCitationCarouselNext /> <InlineCitationCarouselIndex /> </InlineCitationCarouselHeader> <InlineCitationCarouselContent> {#each sources as s (s.url)} <InlineCitationCarouselItem> <InlineCitationSource title={s.title} url={s.url} /> <InlineCitationQuote>{s.quote}</InlineCitationQuote> </InlineCitationCarouselItem> {/each} </InlineCitationCarouselContent> </InlineCitationCarousel> </InlineCitationCardBody> </InlineCitationCard> </InlineCitation> and raised guidance.</p>Component source
Section titled “Component source”"use client";
import { cn } from "cn";import type { ComponentProps } from "react";import { createContext, useCallback, useContext, useEffect, useState,} from "react";import { CaretLeftIcon, CaretRightIcon } from "@phosphor-icons/react";import { Badge } from "@/registry/edmi/ui/badge";import { Button } from "@/registry/edmi/ui/button";import type { CarouselApi } from "@/registry/edmi/ui/carousel";import { Carousel, CarouselContent, CarouselItem,} from "@/registry/edmi/ui/carousel";import { HoverCard, HoverCardContent, HoverCardTrigger,} from "@/registry/edmi/ui/hover-card";
export type InlineCitationProps = ComponentProps<"span">;
export const InlineCitation = ({ className, ...props}: InlineCitationProps) => ( <span data-slot="ai-inline-citation" className={cn("group/citation inline items-center gap-1", className)} {...props} />);
export type InlineCitationTextProps = ComponentProps<"span">;
export const InlineCitationText = ({ className, ...props}: InlineCitationTextProps) => ( <span data-slot="ai-inline-citation-text" className={cn( "transition-colors group-hover/citation:bg-accent", className, )} {...props} />);
export type InlineCitationCardProps = ComponentProps<typeof HoverCard>;
export const InlineCitationCard = (props: InlineCitationCardProps) => ( <HoverCard {...props} />);
export type InlineCitationCardTriggerProps = ComponentProps<typeof Badge> & { sources: string[];};
const hostOf = (url: string) => { try { return new URL(url).hostname.replace(/^www\./, ""); } catch { return url; }};
export const InlineCitationCardTrigger = ({ sources, className, ...props}: InlineCitationCardTriggerProps) => ( <HoverCardTrigger closeDelay={0} delay={0} render={ <Badge data-slot="ai-inline-citation-trigger" className={cn( "ml-1 h-5 cursor-default px-[7px] align-[1px] text-[11.5px] font-normal text-foreground-2", className, )} shape="pill" variant="secondary" {...props} /> } > {sources[0] ? ( <> {hostOf(sources[0])} {sources.length > 1 && `+${sources.length - 1}`} </> ) : ( "unknown" )} </HoverCardTrigger>);
export type InlineCitationCardBodyProps = ComponentProps< typeof HoverCardContent>;
export const InlineCitationCardBody = ({ className, ...props}: InlineCitationCardBodyProps) => ( <HoverCardContent className={cn("relative w-80 overflow-hidden p-0", className)} {...props} />);
const CarouselApiContext = createContext<CarouselApi | undefined>(undefined);
const useCarouselApi = () => { const context = useContext(CarouselApiContext); return context;};
export type InlineCitationCarouselProps = ComponentProps<typeof Carousel>;
export const InlineCitationCarousel = ({ className, children, ...props}: InlineCitationCarouselProps) => { const [api, setApi] = useState<CarouselApi>();
return ( <CarouselApiContext.Provider value={api}> <Carousel className={cn("w-full", className)} setApi={setApi} {...props}> {children} </Carousel> </CarouselApiContext.Provider> );};
export type InlineCitationCarouselContentProps = ComponentProps<"div">;
export const InlineCitationCarouselContent = ( props: InlineCitationCarouselContentProps,) => <CarouselContent {...props} />;
export type InlineCitationCarouselItemProps = ComponentProps<"div">;
// CarouselContent has a -ml-4 gutter, so the left padding is gutter (16) + content inset (14).export const InlineCitationCarouselItem = ({ className, ...props}: InlineCitationCarouselItemProps) => ( <CarouselItem className={cn("w-full space-y-1 py-3 pr-3.5 pl-[30px]", className)} {...props} />);
export type InlineCitationCarouselHeaderProps = ComponentProps<"div">;
export const InlineCitationCarouselHeader = ({ className, ...props}: InlineCitationCarouselHeaderProps) => ( <div data-slot="ai-inline-citation-header" className={cn( "flex items-center gap-0.5 border-b border-border px-2.5 py-2", className, )} {...props} />);
export type InlineCitationCarouselIndexProps = ComponentProps<"div">;
export const InlineCitationCarouselIndex = ({ children, className, ...props}: InlineCitationCarouselIndexProps) => { const api = useCarouselApi(); const [current, setCurrent] = useState(0); const [count, setCount] = useState(0);
const syncState = useCallback(() => { if (!api) { return; } setCount(api.scrollSnapList().length); setCurrent(api.selectedScrollSnap() + 1); }, [api]);
useEffect(() => { if (!api) { return; }
syncState();
api.on("select", syncState);
return () => { api.off("select", syncState); }; }, [api, syncState]);
return ( <div className={cn( "ml-auto font-mono text-xs text-muted-foreground tabular-nums", className, )} {...props} > {children ?? `${current}/${count}`} </div> );};
export type InlineCitationCarouselPrevProps = ComponentProps<typeof Button>;
export const InlineCitationCarouselPrev = ({ className, children, ...props}: InlineCitationCarouselPrevProps) => { const api = useCarouselApi();
const handleClick = useCallback(() => { if (api) { api.scrollPrev(); } }, [api]);
return ( <Button aria-label="Previous" className={className} onClick={handleClick} size="icon-xs" type="button" variant="ghost" {...props} > {children ?? ( <CaretLeftIcon /> )} </Button> );};
export type InlineCitationCarouselNextProps = ComponentProps<typeof Button>;
export const InlineCitationCarouselNext = ({ className, children, ...props}: InlineCitationCarouselNextProps) => { const api = useCarouselApi();
const handleClick = useCallback(() => { if (api) { api.scrollNext(); } }, [api]);
return ( <Button aria-label="Next" className={className} onClick={handleClick} size="icon-xs" type="button" variant="ghost" {...props} > {children ?? ( <CaretRightIcon /> )} </Button> );};
export type InlineCitationSourceProps = ComponentProps<"div"> & { title?: string; url?: string; description?: string;};
export const InlineCitationSource = ({ title, url, description, className, children, ...props}: InlineCitationSourceProps) => ( <div data-slot="ai-inline-citation-source" className={cn("space-y-0.5", className)} {...props} > {title && ( <h4 className="truncate text-[13.5px] leading-tight font-semibold"> {title} </h4> )} {url && ( <p className="truncate font-mono text-[11.5px] text-muted-foreground"> {url.replace(/^https?:\/\//, "")} </p> )} {description && ( <p className="line-clamp-3 text-[13px] leading-relaxed text-muted-foreground"> {description} </p> )} {children} </div>);
export type InlineCitationQuoteProps = ComponentProps<"blockquote">;
export const InlineCitationQuote = ({ children, className, ...props}: InlineCitationQuoteProps) => ( <blockquote data-slot="ai-inline-citation-quote" className={cn( "mt-2.5 border-l-2 border-border pl-2.5 text-[12.5px] text-foreground-2", className, )} {...props} > {children} </blockquote>);<script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <span data-slot="ai-inline-citation" :class="cn('group/citation inline items-center gap-1', props.class)"> <slot /> </span></template><script setup lang="ts">import type { HoverCardRootProps } from "reka-ui"import { HoverCard } from "@/registry/edmi/ui/hover-card"
interface InlineCitationCardProps extends /* @vue-ignore */ HoverCardRootProps {}
defineProps<InlineCitationCardProps>()</script>
<template> <!-- HoverCard props (open, delays) fall through as attrs and win over the 0 defaults. --> <HoverCard :open-delay="0" :close-delay="0"> <slot /> </HoverCard></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"import { HoverCardContent } from "@/registry/edmi/ui/hover-card"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <HoverCardContent :class="cn('relative w-80 overflow-hidden p-0', props.class)"> <slot /> </HoverCardContent></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { computed } from "vue"import { cn } from "@/registry/edmi/lib/utils"import { Badge } from "@/registry/edmi/ui/badge"import { HoverCardTrigger } from "@/registry/edmi/ui/hover-card"
const props = defineProps<{ /** Source URLs; shows the first host and `+N`. */ sources: string[] class?: HTMLAttributes["class"]}>()
const displayText = computed(() => { const first = props.sources[0] if (!first) return "unknown" let host = first try { host = new URL(first).hostname.replace(/^www\./, "") } catch {} const more = props.sources.length - 1 return more > 0 ? `${host} +${more}` : host})</script>
<template> <HoverCardTrigger as-child> <Badge as="span" data-slot="ai-inline-citation-trigger" shape="pill" variant="secondary" :class="cn('ml-1 h-5 cursor-default px-[7px] align-[1px] text-[11.5px] font-normal text-foreground-2', props.class)" > {{ displayText }} </Badge> </HoverCardTrigger></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"import { Carousel } from "@/registry/edmi/ui/carousel"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <Carousel :class="cn('w-full', props.class)"> <slot /> </Carousel></template><script setup lang="ts">import { CarouselContent } from "@/registry/edmi/ui/carousel"</script>
<template> <CarouselContent> <slot /> </CarouselContent></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <div data-slot="ai-inline-citation-header" :class="cn('flex items-center gap-0.5 border-b border-border px-2.5 py-2', props.class)" > <slot /> </div></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { computed, ref, watch } from "vue"import { cn } from "@/registry/edmi/lib/utils"import { useCarousel } from "@/registry/edmi/ui/carousel"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()
const { carouselApi } = useCarousel()const current = ref(0)const count = ref(0)
const displayText = computed(() => `${current.value}/${count.value}`)
function updateState() { const api = carouselApi.value if (!api) return count.value = api.scrollSnapList().length current.value = api.selectedScrollSnap() + 1}
watch(carouselApi, (api, _, onCleanup) => { if (!api) return updateState() api.on("select", updateState) api.on("reInit", updateState) onCleanup(() => { api.off("select", updateState) api.off("reInit", updateState) })}, { immediate: true })</script>
<template> <div :class="cn('ml-auto font-mono text-xs text-muted-foreground tabular-nums', props.class)"> <slot>{{ displayText }}</slot> </div></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"import { CarouselItem } from "@/registry/edmi/ui/carousel"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <!-- CarouselContent has a -ml-4 gutter, so the left padding is gutter (16) + content inset (14). --> <CarouselItem :class="cn('w-full space-y-1 py-3 pr-3.5 pl-[30px]', props.class)"> <slot /> </CarouselItem></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { ChevronRightIcon } from "@lucide/vue"import { Button } from "@/registry/edmi/ui/button"import { useCarousel } from "@/registry/edmi/ui/carousel"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()
const { scrollNext } = useCarousel()</script>
<template> <Button type="button" variant="ghost" size="icon-xs" aria-label="Next" :class="props.class" @click="scrollNext" > <slot><ChevronRightIcon /></slot> </Button></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { ChevronLeftIcon } from "@lucide/vue"import { Button } from "@/registry/edmi/ui/button"import { useCarousel } from "@/registry/edmi/ui/carousel"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()
const { scrollPrev } = useCarousel()</script>
<template> <Button type="button" variant="ghost" size="icon-xs" aria-label="Previous" :class="props.class" @click="scrollPrev" > <slot><ChevronLeftIcon /></slot> </Button></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <blockquote data-slot="ai-inline-citation-quote" :class="cn('mt-2.5 border-l-2 border-border pl-2.5 text-[12.5px] text-foreground-2', props.class)" > <slot /> </blockquote></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { computed } from "vue"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ title?: string url?: string description?: string class?: HTMLAttributes["class"]}>()
const shownUrl = computed(() => props.url?.replace(/^https?:\/\//, ""))</script>
<template> <div data-slot="ai-inline-citation-source" :class="cn('space-y-0.5', props.class)"> <h4 v-if="title" class="truncate text-[13.5px] leading-tight font-semibold"> {{ title }} </h4> <p v-if="shownUrl" class="truncate font-mono text-[11.5px] text-muted-foreground"> {{ shownUrl }} </p> <p v-if="description" class="line-clamp-3 text-[13px] leading-relaxed text-muted-foreground"> {{ description }} </p> <slot /> </div></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <span data-slot="ai-inline-citation-text" :class="cn('transition-colors group-hover/citation:bg-accent', props.class)"> <slot /> </span></template>export { default as InlineCitation } from "./InlineCitation.vue";export { default as InlineCitationCard } from "./InlineCitationCard.vue";export { default as InlineCitationCardBody } from "./InlineCitationCardBody.vue";export { default as InlineCitationCardTrigger } from "./InlineCitationCardTrigger.vue";export { default as InlineCitationCarousel } from "./InlineCitationCarousel.vue";export { default as InlineCitationCarouselContent } from "./InlineCitationCarouselContent.vue";export { default as InlineCitationCarouselHeader } from "./InlineCitationCarouselHeader.vue";export { default as InlineCitationCarouselIndex } from "./InlineCitationCarouselIndex.vue";export { default as InlineCitationCarouselItem } from "./InlineCitationCarouselItem.vue";export { default as InlineCitationCarouselNext } from "./InlineCitationCarouselNext.vue";export { default as InlineCitationCarouselPrev } from "./InlineCitationCarouselPrev.vue";export { default as InlineCitationQuote } from "./InlineCitationQuote.vue";export { default as InlineCitationSource } from "./InlineCitationSource.vue";export { default as InlineCitationText } from "./InlineCitationText.vue";import Root from "./inline-citation.svelte";import Card from "./inline-citation-card.svelte";import CardBody from "./inline-citation-card-body.svelte";import CardTrigger from "./inline-citation-card-trigger.svelte";import Carousel from "./inline-citation-carousel.svelte";import CarouselContent from "./inline-citation-carousel-content.svelte";import CarouselHeader from "./inline-citation-carousel-header.svelte";import CarouselIndex from "./inline-citation-carousel-index.svelte";import CarouselItem from "./inline-citation-carousel-item.svelte";import CarouselNext from "./inline-citation-carousel-next.svelte";import CarouselPrev from "./inline-citation-carousel-prev.svelte";import Quote from "./inline-citation-quote.svelte";import Source from "./inline-citation-source.svelte";import Text from "./inline-citation-text.svelte";
export { Card, Card as InlineCitationCard, CardBody, CardBody as InlineCitationCardBody, CardTrigger, CardTrigger as InlineCitationCardTrigger, Carousel, Carousel as InlineCitationCarousel, CarouselContent, CarouselContent as InlineCitationCarouselContent, CarouselHeader, CarouselHeader as InlineCitationCarouselHeader, CarouselIndex, CarouselIndex as InlineCitationCarouselIndex, CarouselItem, CarouselItem as InlineCitationCarouselItem, CarouselNext, CarouselNext as InlineCitationCarouselNext, CarouselPrev, CarouselPrev as InlineCitationCarouselPrev, Quote, Quote as InlineCitationQuote, Root, // Root as InlineCitation, Source, Source as InlineCitationSource, Text, Text as InlineCitationText,};<script lang="ts"> import { cn } from "$lib/utils.js"; import { HoverCardContent } from "$lib/registry/ui/hover-card/index.js"; import type { ComponentProps } from "svelte";
let { class: className, children, ...restProps }: ComponentProps<typeof HoverCardContent> = $props();</script>
<HoverCardContent class={cn("relative w-80 overflow-hidden p-0", className)} {...restProps}> {@render children?.()}</HoverCardContent><script lang="ts"> import { cn } from "$lib/utils.js"; import { HoverCardTrigger } from "$lib/registry/ui/hover-card/index.js"; import { badgeVariants } from "$lib/registry/ui/badge/index.js"; import type { ComponentProps } from "svelte";
let { class: className, sources, ...restProps }: Omit<ComponentProps<typeof HoverCardTrigger>, "children"> & { /** Source URLs; shows the first host and `+N`. */ sources: string[]; } = $props();
const text = $derived.by(() => { const first = sources[0]; if (!first) return "unknown"; let host = first; try { host = new URL(first).hostname.replace(/^www\./, ""); } catch {} const more = sources.length - 1; return more > 0 ? `${host} +${more}` : host; });</script>
<!-- A span trigger styled as the secondary pill badge. --><HoverCardTrigger data-slot="ai-inline-citation-trigger" class={cn( badgeVariants({ variant: "secondary", shape: "pill" }), "ml-1 h-5 cursor-default px-[7px] align-[1px] text-[11.5px] font-normal text-foreground-2", className )} {...restProps}> {text}</HoverCardTrigger><script lang="ts"> import { HoverCard } from "$lib/registry/ui/hover-card/index.js"; import type { ComponentProps } from "svelte";
let { open = $bindable(false), openDelay = 0, closeDelay = 0, children, ...restProps }: ComponentProps<typeof HoverCard> = $props();</script>
<HoverCard bind:open {openDelay} {closeDelay} {...restProps}> {@render children?.()}</HoverCard><script lang="ts"> import { CarouselContent } from "$lib/registry/ui/carousel/index.js"; import type { ComponentProps } from "svelte";
let { children, ...restProps }: ComponentProps<typeof CarouselContent> = $props();</script>
<CarouselContent {...restProps}> {@render children?.()}</CarouselContent><script lang="ts"> import { cn } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> = $props();</script>
<div data-slot="ai-inline-citation-header" class={cn("flex items-center gap-0.5 border-b border-border px-2.5 py-2", className)} {...restProps}> {@render children?.()}</div><script lang="ts"> import { cn } from "$lib/utils.js"; import { getEmblaContext } from "$lib/registry/ui/carousel/context.js"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> = $props();
const embla = getEmblaContext("<InlineCitationCarouselIndex/>");</script>
<div class={cn("ml-auto font-mono text-xs text-muted-foreground tabular-nums", className)} {...restProps}> {#if children} {@render children()} {:else} {embla.selectedIndex + 1}/{embla.scrollSnaps.length} {/if}</div><script lang="ts"> import { cn } from "$lib/utils.js"; import { CarouselItem } from "$lib/registry/ui/carousel/index.js"; import type { ComponentProps } from "svelte";
let { class: className, children, ...restProps }: ComponentProps<typeof CarouselItem> = $props();</script>
<!-- CarouselContent has a -ms-4 gutter, so the left padding is gutter (16) + content inset (14). --><CarouselItem class={cn("w-full space-y-1 py-3 pr-3.5 ps-[30px]", className)} {...restProps}> {@render children?.()}</CarouselItem><script lang="ts"> import CaretRightIcon from "phosphor-svelte/lib/CaretRight"; import { Button, type ButtonProps } from "$lib/registry/ui/button/index.js"; import { getEmblaContext } from "$lib/registry/ui/carousel/context.js";
let { children, ...restProps }: Omit<ButtonProps, "href" | "onclick"> = $props();
const embla = getEmblaContext("<InlineCitationCarouselNext/>");</script>
<Button type="button" variant="ghost" size="icon-xs" aria-label="Next" onclick={() => embla.scrollNext()} {...restProps}> {#if children} {@render children()} {:else} <CaretRightIcon /> {/if}</Button><script lang="ts"> import CaretLeftIcon from "phosphor-svelte/lib/CaretLeft"; import { Button, type ButtonProps } from "$lib/registry/ui/button/index.js"; import { getEmblaContext } from "$lib/registry/ui/carousel/context.js";
let { children, ...restProps }: Omit<ButtonProps, "href" | "onclick"> = $props();
const embla = getEmblaContext("<InlineCitationCarouselPrev/>");</script>
<Button type="button" variant="ghost" size="icon-xs" aria-label="Previous" onclick={() => embla.scrollPrev()} {...restProps}> {#if children} {@render children()} {:else} <CaretLeftIcon /> {/if}</Button><script lang="ts"> import { cn } from "$lib/utils.js"; import { Carousel } from "$lib/registry/ui/carousel/index.js"; import type { ComponentProps } from "svelte";
let { class: className, children, ...restProps }: ComponentProps<typeof Carousel> = $props();</script>
<Carousel class={cn("w-full", className)} {...restProps}> {@render children?.()}</Carousel><script lang="ts"> import { cn } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLQuoteElement> = $props();</script>
<blockquote data-slot="ai-inline-citation-quote" class={cn("mt-2.5 border-l-2 border-border pl-2.5 text-[12.5px] text-foreground-2", className)} {...restProps}> {@render children?.()}</blockquote><script lang="ts"> import { cn } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, title, url, description, children, ...restProps }: HTMLAttributes<HTMLDivElement> & { title?: string; url?: string; description?: string; } = $props();</script>
<div data-slot="ai-inline-citation-source" class={cn("space-y-0.5", className)} {...restProps}> {#if title} <h4 class="truncate text-[13.5px] leading-tight font-semibold">{title}</h4> {/if} {#if url} <p class="truncate font-mono text-[11.5px] text-muted-foreground">{url.replace(/^https?:\/\//, "")}</p> {/if} {#if description} <p class="line-clamp-3 text-[13px] leading-relaxed text-muted-foreground">{description}</p> {/if} {@render children?.()}</div><script lang="ts"> import { cn } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLSpanElement> = $props();</script>
<span data-slot="ai-inline-citation-text" class={cn("transition-colors group-hover/citation:bg-accent", className)} {...restProps}> {@render children?.()}</span><script lang="ts"> import { cn } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLSpanElement> = $props();</script>
<span data-slot="ai-inline-citation" class={cn("group/citation inline items-center gap-1", className)} {...restProps}> {@render children?.()}</span>| Part | Prop | Type | Default |
|---|---|---|---|
| InlineCitationCardTrigger | sources |
string[] (URLs; shows the first host and +N) |
|
| InlineCitationCarousel | Carousel props | ||
| InlineCitationCarouselHeader | arrows left, index right | ||
| InlineCitationSource | title, url, description |
string |
|
| InlineCitationQuote | blockquote |
✦ Edmi additions
Section titled “✦ Edmi additions”- The hover card opens instantly (no delay) like the stock component and is a solid
--popoversurface. - Header arrows are ghost
icon-xsbuttons; the page index is mono. - The quote is a 2px
--borderrule, not italic.