"use client"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import { cn } from "@/lib/utils"; import { CheckIcon, CopyIcon, FileIcon, GitCommitIcon, MinusIcon, PlusIcon, } from "lucide-react"; import type { ComponentProps, HTMLAttributes } from "react"; import { useCallback, useEffect, useRef, useState } from "react"; export type CommitProps = ComponentProps; export const Commit = ({ className, children, ...props }: CommitProps) => ( {children} ); export type CommitHeaderProps = ComponentProps; export const CommitHeader = ({ className, children, ...props }: CommitHeaderProps) => (
{children}
); export type CommitHashProps = HTMLAttributes; export const CommitHash = ({ className, children, ...props }: CommitHashProps) => ( {children} ); export type CommitMessageProps = HTMLAttributes; export const CommitMessage = ({ className, children, ...props }: CommitMessageProps) => ( {children} ); export type CommitMetadataProps = HTMLAttributes; export const CommitMetadata = ({ className, children, ...props }: CommitMetadataProps) => (
{children}
); export type CommitSeparatorProps = HTMLAttributes; export const CommitSeparator = ({ className, children, ...props }: CommitSeparatorProps) => ( {children ?? "•"} ); export type CommitInfoProps = HTMLAttributes; export const CommitInfo = ({ className, children, ...props }: CommitInfoProps) => (
{children}
); export type CommitAuthorProps = HTMLAttributes; export const CommitAuthor = ({ className, children, ...props }: CommitAuthorProps) => (
{children}
); export type CommitAuthorAvatarProps = ComponentProps & { initials: string; }; export const CommitAuthorAvatar = ({ initials, className, ...props }: CommitAuthorAvatarProps) => ( {initials} ); export type CommitTimestampProps = HTMLAttributes & { date: Date; }; const relativeTimeFormat = new Intl.RelativeTimeFormat("en", { numeric: "auto", }); const formatRelativeDate = (date: Date) => { const days = Math.round( (date.getTime() - Date.now()) / (1000 * 60 * 60 * 24) ); return relativeTimeFormat.format(days, "day"); }; export const CommitTimestamp = ({ date, className, children, ...props }: CommitTimestampProps) => { const [formatted, setFormatted] = useState(""); const updateFormatted = useCallback(() => { setFormatted(formatRelativeDate(date)); }, [date]); useEffect(() => { updateFormatted(); }, [updateFormatted]); return ( ); }; export type CommitActionsProps = HTMLAttributes; const handleActionsClick = (e: React.MouseEvent) => e.stopPropagation(); const handleActionsKeyDown = (e: React.KeyboardEvent) => e.stopPropagation(); export const CommitActions = ({ className, children, ...props }: CommitActionsProps) => (
{children}
); export type CommitCopyButtonProps = ComponentProps & { hash: string; onCopy?: () => void; onError?: (error: Error) => void; timeout?: number; }; export const CommitCopyButton = ({ hash, onCopy, onError, timeout = 2000, children, className, ...props }: CommitCopyButtonProps) => { const [isCopied, setIsCopied] = useState(false); const timeoutRef = useRef(0); const copyToClipboard = useCallback(async () => { if (typeof window === "undefined" || !navigator?.clipboard?.writeText) { onError?.(new Error("Clipboard API not available")); return; } try { if (!isCopied) { await navigator.clipboard.writeText(hash); setIsCopied(true); onCopy?.(); timeoutRef.current = window.setTimeout( () => setIsCopied(false), timeout ); } } catch (error) { onError?.(error as Error); } }, [hash, onCopy, onError, timeout, isCopied]); useEffect( () => () => { window.clearTimeout(timeoutRef.current); }, [] ); const Icon = isCopied ? CheckIcon : CopyIcon; return ( ); }; export type CommitContentProps = ComponentProps; export const CommitContent = ({ className, children, ...props }: CommitContentProps) => ( {children} ); export type CommitFilesProps = HTMLAttributes; export const CommitFiles = ({ className, children, ...props }: CommitFilesProps) => (
{children}
); export type CommitFileProps = HTMLAttributes; export const CommitFile = ({ className, children, ...props }: CommitFileProps) => (
{children}
); export type CommitFileInfoProps = HTMLAttributes; export const CommitFileInfo = ({ className, children, ...props }: CommitFileInfoProps) => (
{children}
); const fileStatusStyles = { added: "text-green-600 dark:text-green-400", deleted: "text-red-600 dark:text-red-400", modified: "text-yellow-600 dark:text-yellow-400", renamed: "text-blue-600 dark:text-blue-400", }; const fileStatusLabels = { added: "A", deleted: "D", modified: "M", renamed: "R", }; export type CommitFileStatusProps = HTMLAttributes & { status: "added" | "modified" | "deleted" | "renamed"; }; export const CommitFileStatus = ({ status, className, children, ...props }: CommitFileStatusProps) => ( {children ?? fileStatusLabels[status]} ); export type CommitFileIconProps = ComponentProps; export const CommitFileIcon = ({ className, ...props }: CommitFileIconProps) => ( ); export type CommitFilePathProps = HTMLAttributes; export const CommitFilePath = ({ className, children, ...props }: CommitFilePathProps) => ( {children} ); export type CommitFileChangesProps = HTMLAttributes; export const CommitFileChanges = ({ className, children, ...props }: CommitFileChangesProps) => (
{children}
); export type CommitFileAdditionsProps = HTMLAttributes & { count: number; }; export const CommitFileAdditions = ({ count, className, children, ...props }: CommitFileAdditionsProps) => { if (count <= 0) { return null; } return ( {children ?? ( <> {count} )} ); }; export type CommitFileDeletionsProps = HTMLAttributes & { count: number; }; export const CommitFileDeletions = ({ count, className, children, ...props }: CommitFileDeletionsProps) => { if (count <= 0) { return null; } return ( {children ?? ( <> {count} )} ); };