Download src/components/shared/SharedEmptyState.tsx from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 6.54 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/shared/SharedEmptyState.tsx
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/components/shared/SharedEmptyState.tsx
-
curl -L -o SharedEmptyState.tsx https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/shared/SharedEmptyState.tsx
6.54 kB
| "use client"; | |
| import Link from "next/link"; | |
| import { HTMLAttributes } from "react"; | |
| import { clsx } from "clsx"; | |
| // ============================================================================= | |
| // EmptyState Types | |
| // ============================================================================= | |
| export interface EmptyStateProps extends HTMLAttributes<HTMLDivElement> { | |
| icon?: React.ReactNode; | |
| title: string; | |
| description?: string; | |
| actionText?: string; | |
| actionHref?: string; | |
| actionOnClick?: () => void; | |
| actionVariant?: "primary" | "secondary" | "ghost"; | |
| compact?: boolean; | |
| } | |
| // ============================================================================= | |
| // SharedEmptyState Component | |
| // ============================================================================= | |
| export function SharedEmptyState({ | |
| icon, | |
| title, | |
| description, | |
| actionText, | |
| actionHref, | |
| actionOnClick, | |
| actionVariant = "primary", | |
| compact = false, | |
| className, | |
| ...props | |
| }: EmptyStateProps) { | |
| const variantClasses = { | |
| primary: "bg-[var(--accent-primary)] text-white hover:bg-[var(--accent-primary-hover)]", | |
| secondary: "bg-[var(--bg-surface)] text-[var(--text-primary)] border border-[var(--border-subtle)] hover:bg-[var(--bg-elevated)]", | |
| ghost: "bg-transparent text-[var(--text-secondary)] hover:bg-[var(--bg-glass)]", | |
| }; | |
| return ( | |
| <div | |
| className={clsx( | |
| "flex flex-col items-center justify-center", | |
| compact ? "gap-3 p-6" : "gap-4 p-8 sm:p-12", | |
| "rounded-xl", | |
| "border border-[var(--border-subtle)]", | |
| "bg-[var(--bg-glass)]", | |
| "text-center", | |
| className, | |
| )} | |
| {...props} | |
| > | |
| {/* Icon */} | |
| {icon && ( | |
| <div | |
| className={clsx( | |
| "grid place-items-center", | |
| compact ? "size-10" : "size-14", | |
| "rounded-full", | |
| "bg-[var(--accent-primary)]/10", | |
| )} | |
| > | |
| {icon} | |
| </div> | |
| )} | |
| {/* Title */} | |
| <h3 | |
| className={clsx( | |
| compact ? "text-[16px]" : "text-[18px]", | |
| "font-bold", | |
| "text-[var(--text-primary)]", | |
| )} | |
| > | |
| {title} | |
| </h3> | |
| {/* Description */} | |
| {description && ( | |
| <p | |
| className={clsx( | |
| compact ? "text-[13px]" : "text-[14px]", | |
| "leading-relaxed", | |
| "text-[var(--text-secondary)]", | |
| "max-w-[400px]", | |
| )} | |
| > | |
| {description} | |
| </p> | |
| )} | |
| {/* Action */} | |
| {(actionText || actionHref || actionOnClick) && ( | |
| <div className="mt-2"> | |
| {actionHref ? ( | |
| <Link | |
| href={actionHref} | |
| className={clsx( | |
| "inline-flex items-center justify-center gap-2", | |
| "h-[40px] px-4", | |
| "rounded-lg", | |
| "text-[13px] font-semibold", | |
| "transition", | |
| variantClasses[actionVariant], | |
| )} | |
| > | |
| {actionText} | |
| </Link> | |
| ) : actionOnClick ? ( | |
| <button | |
| type="button" | |
| onClick={actionOnClick} | |
| className={clsx( | |
| "inline-flex items-center justify-center gap-2", | |
| "h-[40px] px-4", | |
| "rounded-lg", | |
| "text-[13px] font-semibold", | |
| "transition", | |
| variantClasses[actionVariant], | |
| )} | |
| > | |
| {actionText} | |
| </button> | |
| ) : null} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |
| // ============================================================================= | |
| // Convenience Empty State Components | |
| // ============================================================================= | |
| // No results empty state | |
| export function NoResultsEmptyState({ | |
| searchQuery, | |
| onClear, | |
| className, | |
| ...props | |
| }: { searchQuery?: string; onClear?: () => void } & Omit<EmptyStateProps, "title" | "description">) { | |
| return ( | |
| <SharedEmptyState | |
| title="No results found" | |
| description={ | |
| searchQuery | |
| ? `No results match "${searchQuery}". Try different keywords.` | |
| : "No items found. Check back later." | |
| } | |
| actionText={searchQuery ? "Clear search" : undefined} | |
| actionOnClick={onClear} | |
| actionVariant="ghost" | |
| className={className} | |
| {...props} | |
| /> | |
| ); | |
| } | |
| // No content empty state | |
| export function NoContentEmptyState({ | |
| type, | |
| onCreate, | |
| className, | |
| ...props | |
| }: { type: string; onCreate?: () => void } & Omit<EmptyStateProps, "title" | "description">) { | |
| const messages: Record<string, { title: string; description: string }> = { | |
| documents: { | |
| title: "No documents uploaded", | |
| description: "Upload your PDFs or notes to start studying with DocDoe.", | |
| }, | |
| chapters: { | |
| title: "No chapters found", | |
| description: "Select a subject to see available chapters.", | |
| }, | |
| progress: { | |
| title: "No study progress yet", | |
| description: "Complete lessons and quizzes to track your progress.", | |
| }, | |
| bookmarks: { | |
| title: "No bookmarks saved", | |
| description: "Save important topics to revisit later.", | |
| }, | |
| default: { | |
| title: "No content available", | |
| description: "Check back later for updates.", | |
| }, | |
| }; | |
| const message = messages[type] || messages.default; | |
| return ( | |
| <SharedEmptyState | |
| title={message.title} | |
| description={message.description} | |
| actionText={onCreate ? `Add ${type}` : undefined} | |
| actionOnClick={onCreate} | |
| className={className} | |
| {...props} | |
| /> | |
| ); | |
| } | |
| // Error empty state | |
| export function ErrorEmptyState({ | |
| errorMessage, | |
| onRetry, | |
| className, | |
| ...props | |
| }: { errorMessage?: string; onRetry?: () => void } & Omit<EmptyStateProps, "title" | "description">) { | |
| return ( | |
| <SharedEmptyState | |
| title="Could not complete this" | |
| description={errorMessage || "We couldn't load this content. Please try again."} | |
| actionText="Try again" | |
| actionOnClick={onRetry} | |
| actionVariant="primary" | |
| className={className} | |
| {...props} | |
| /> | |
| ); | |
| } | |
| // Loading placeholder (not a skeleton, but a full empty state) | |
| export function LoadingEmptyState({ | |
| message = "Loading...", | |
| className, | |
| ...props | |
| }: { message?: string } & Omit<EmptyStateProps, "title" | "description">) { | |
| return ( | |
| <SharedEmptyState | |
| title="" | |
| description={message} | |
| compact | |
| className={clsx("min-h-[120px]", className)} | |
| {...props} | |
| /> | |
| ); | |
| } | |