Spaces:
Paused
Paused
| 'use client'; | |
| import { type ComponentProps, createContext, useCallback, useContext, useEffect, useState } from 'react'; | |
| import { ArrowLeftIcon, ArrowRightIcon } from 'lucide-react'; | |
| import { cn } from '@/lib/utils'; | |
| import { Badge } from '@/components/ui/badge'; | |
| import { Carousel, type CarouselApi, CarouselContent, CarouselItem } from '@/components/ui/carousel'; | |
| import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'; | |
| export type InlineCitationProps = ComponentProps<'span'>; | |
| export const InlineCitation = ({ className, ...props }: InlineCitationProps) => ( | |
| <span className={cn('group inline items-center gap-1', className)} {...props} /> | |
| ); | |
| export type InlineCitationTextProps = ComponentProps<'span'>; | |
| export const InlineCitationText = ({ className, ...props }: InlineCitationTextProps) => ( | |
| <span className={cn('group-hover:bg-accent transition-colors', className)} {...props} /> | |
| ); | |
| export type InlineCitationCardProps = ComponentProps<typeof HoverCard>; | |
| export const InlineCitationCard = (props: InlineCitationCardProps) => <HoverCard closeDelay={0} openDelay={0} {...props} />; | |
| export type InlineCitationCardTriggerProps = ComponentProps<typeof Badge> & { | |
| sources: string[]; | |
| }; | |
| export const InlineCitationCardTrigger = ({ sources, className, ...props }: InlineCitationCardTriggerProps) => ( | |
| <HoverCardTrigger asChild> | |
| <Badge className={cn('ml-1 rounded-full', className)} variant='secondary' {...props}> | |
| {sources[0] ? ( | |
| <> | |
| {new URL(sources[0]).hostname} {sources.length > 1 && `+${sources.length - 1}`} | |
| </> | |
| ) : ( | |
| 'unknown' | |
| )} | |
| </Badge> | |
| </HoverCardTrigger> | |
| ); | |
| export type InlineCitationCardBodyProps = ComponentProps<'div'>; | |
| export const InlineCitationCardBody = ({ className, ...props }: InlineCitationCardBodyProps) => ( | |
| <HoverCardContent className={cn('relative w-80 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'>; | |
| export const InlineCitationCarouselItem = ({ className, ...props }: InlineCitationCarouselItemProps) => ( | |
| <CarouselItem className={cn('w-full space-y-2 p-4 pl-8', className)} {...props} /> | |
| ); | |
| export type InlineCitationCarouselHeaderProps = ComponentProps<'div'>; | |
| export const InlineCitationCarouselHeader = ({ className, ...props }: InlineCitationCarouselHeaderProps) => ( | |
| <div className={cn('bg-secondary flex items-center justify-between gap-2 rounded-t-md p-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); | |
| useEffect(() => { | |
| if (!api) { | |
| return; | |
| } | |
| setCount(api.scrollSnapList().length); | |
| setCurrent(api.selectedScrollSnap() + 1); | |
| api.on('select', () => { | |
| setCurrent(api.selectedScrollSnap() + 1); | |
| }); | |
| }, [api]); | |
| return ( | |
| <div className={cn('text-muted-foreground flex flex-1 items-center justify-end px-3 py-1 text-xs', className)} {...props}> | |
| {children ?? `${current}/${count}`} | |
| </div> | |
| ); | |
| }; | |
| export type InlineCitationCarouselPrevProps = ComponentProps<'button'>; | |
| export const InlineCitationCarouselPrev = ({ className, ...props }: InlineCitationCarouselPrevProps) => { | |
| const api = useCarouselApi(); | |
| const handleClick = useCallback(() => { | |
| if (api) { | |
| api.scrollPrev(); | |
| } | |
| }, [api]); | |
| return ( | |
| <button aria-label='Previous' className={cn('shrink-0', className)} onClick={handleClick} type='button' {...props}> | |
| <ArrowLeftIcon className='text-muted-foreground size-4' /> | |
| </button> | |
| ); | |
| }; | |
| export type InlineCitationCarouselNextProps = ComponentProps<'button'>; | |
| export const InlineCitationCarouselNext = ({ className, ...props }: InlineCitationCarouselNextProps) => { | |
| const api = useCarouselApi(); | |
| const handleClick = useCallback(() => { | |
| if (api) { | |
| api.scrollNext(); | |
| } | |
| }, [api]); | |
| return ( | |
| <button aria-label='Next' className={cn('shrink-0', className)} onClick={handleClick} type='button' {...props}> | |
| <ArrowRightIcon className='text-muted-foreground size-4' /> | |
| </button> | |
| ); | |
| }; | |
| export type InlineCitationSourceProps = ComponentProps<'div'> & { | |
| title?: string; | |
| url?: string; | |
| description?: string; | |
| }; | |
| export const InlineCitationSource = ({ title, url, description, className, children, ...props }: InlineCitationSourceProps) => ( | |
| <div className={cn('space-y-1', className)} {...props}> | |
| {title && <h4 className='truncate text-sm leading-tight font-medium'>{title}</h4>} | |
| {url && <p className='text-muted-foreground truncate text-xs break-all'>{url}</p>} | |
| {description && <p className='text-muted-foreground line-clamp-3 text-sm leading-relaxed'>{description}</p>} | |
| {children} | |
| </div> | |
| ); | |
| export type InlineCitationQuoteProps = ComponentProps<'blockquote'>; | |
| export const InlineCitationQuote = ({ children, className, ...props }: InlineCitationQuoteProps) => ( | |
| <blockquote className={cn('border-muted text-muted-foreground border-l-2 pl-3 text-sm italic', className)} {...props}> | |
| {children} | |
| </blockquote> | |
| ); | |