| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- 'use client';
- 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';
- import { cn } from '@/lib/utils';
- import { ArrowLeftIcon, ArrowRightIcon } from 'lucide-react';
- import {
- type ComponentProps,
- createContext,
- useCallback,
- useContext,
- useEffect,
- useState,
- } from 'react';
- 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('transition-colors group-hover:bg-accent', 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(
- 'flex items-center justify-between gap-2 rounded-t-md bg-secondary p-2',
- className,
- )}
- {...props}
- />
- );
- export type InlineCitationCarouselIndexProps = ComponentProps<'div'>;
- export const InlineCitationCarouselIndex = ({
- children,
- className,
- ...props
- }: InlineCitationCarouselIndexProps) => {
- const api = useCarouselApi();
- const count = api?.scrollSnapList().length ?? 0;
- const [current, setCurrent] = useState(0);
- useEffect(() => {
- if (!api) {
- return;
- }
- // eslint-disable-next-line react-hooks/set-state-in-effect -- Initial sync from external embla carousel API
- setCurrent(api.selectedScrollSnap() + 1);
- const onSelect = () => {
- setCurrent(api.selectedScrollSnap() + 1);
- };
- api.on('select', onSelect);
- return () => {
- api.off('select', onSelect);
- };
- }, [api]);
- return (
- <div
- className={cn(
- 'flex flex-1 items-center justify-end px-3 py-1 text-muted-foreground 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="size-4 text-muted-foreground" />
- </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="size-4 text-muted-foreground" />
- </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 font-medium text-sm leading-tight">{title}</h4>}
- {url && <p className="truncate break-all text-muted-foreground text-xs">{url}</p>}
- {description && (
- <p className="line-clamp-3 text-muted-foreground 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 border-l-2 pl-3 text-muted-foreground text-sm italic', className)}
- {...props}
- >
- {children}
- </blockquote>
- );
|