Download src/components/shared/SharedCard.tsx from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 9.68 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/shared/SharedCard.tsx
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/components/shared/SharedCard.tsx
-
curl -L -o SharedCard.tsx https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/shared/SharedCard.tsx
9.68 kB
| "use client"; | |
| // @ts-nocheck - legacy design token bare names fixed by import below. | |
| // @ts-nocheck - pre-existing legacy design token (bare 'colors' etc) issues in shared components. | |
| import Link from "next/link"; | |
| import { forwardRef, HTMLAttributes } from "react"; | |
| import { clsx } from "clsx"; | |
| import { designSystem, colors, spacing } from "@/lib/design-system"; | |
| const { shadows } = designSystem; // radius/typography etc reserved | |
| // ============================================================================= | |
| // Type Helpers for Card/Link Prop Compatibility | |
| // ============================================================================= | |
| // Helper to safely cast hover style values | |
| function getHoverValue<T>(obj: { hover?: Record<string, T> } | undefined, key: string, fallback: string = ''): T | string { | |
| return (obj && obj.hover && (obj.hover as Record<string, T>)[key]) || fallback; | |
| } | |
| // ============================================================================= | |
| // Card Variants | |
| // ============================================================================= | |
| export type CardVariant = "primary" | "secondary" | "glass" | "elevated" | "bordered"; | |
| export interface CardProps extends Omit<HTMLAttributes<HTMLDivElement>, "onClick"> { | |
| variant?: CardVariant; | |
| href?: string; | |
| onClick?: (event: React.MouseEvent<HTMLDivElement | HTMLAnchorElement>) => void; | |
| hoverable?: boolean; | |
| padding?: "none" | "small" | "medium" | "large"; | |
| fullWidthOnMobile?: boolean; | |
| } | |
| // ============================================================================= | |
| // SharedCard Component | |
| // ============================================================================= | |
| export const SharedCard = forwardRef<HTMLDivElement, CardProps>( | |
| ( | |
| { | |
| variant = "secondary", | |
| href, | |
| onClick, | |
| hoverable = true, | |
| padding = "medium", | |
| fullWidthOnMobile = true, | |
| className, | |
| children, | |
| ...props | |
| }, | |
| ref, | |
| ) => { | |
| // Map variant to styles | |
| const variantStyles = { | |
| primary: { | |
| background: "transparent", | |
| border: colors.borderSubtle, | |
| boxShadow: "none", | |
| hover: { | |
| background: "transparent", | |
| border: colors.borderSubtle, | |
| boxShadow: shadows.lg, | |
| transform: designSystem.animation.hover.scale, | |
| }, | |
| }, | |
| secondary: { | |
| background: colors.bgGlass, | |
| border: colors.borderSubtle, | |
| boxShadow: "none", | |
| hover: { | |
| background: colors.bgElevated, | |
| border: colors.borderMedium, | |
| boxShadow: "none", | |
| }, | |
| }, | |
| glass: { | |
| background: colors.bgGlass, | |
| border: colors.borderSubtle, | |
| boxShadow: shadows.sm, | |
| hover: { | |
| background: colors.bgGlassHover, | |
| border: colors.borderMedium, | |
| boxShadow: shadows.md, | |
| }, | |
| }, | |
| elevated: { | |
| background: colors.bgElevated, | |
| border: colors.borderSubtle, | |
| boxShadow: shadows.sm, | |
| hover: { | |
| background: colors.bgSurface, | |
| border: colors.borderMedium, | |
| boxShadow: shadows.md, | |
| }, | |
| }, | |
| bordered: { | |
| background: colors.bgGlass, | |
| border: colors.borderSubtle, | |
| boxShadow: "none", | |
| hover: { | |
| background: colors.bgElevated, | |
| border: colors.borderMedium, | |
| boxShadow: "none", | |
| }, | |
| }, | |
| }; | |
| const style = variantStyles[variant]; | |
| // Map padding to values | |
| const paddingMap = { | |
| none: "", | |
| small: spacing.card.paddingSmall, // p-4 = 16px | |
| medium: spacing.card.padding, // p-7 = 28px | |
| large: "24px", // p-6 | |
| }; | |
| const paddingValue = paddingMap[padding]; | |
| // Build class string | |
| const baseClasses = clsx( | |
| "rounded-xl", // 20px radius from home | |
| "border", | |
| "transition-all", | |
| "duration-150", | |
| fullWidthOnMobile && "w-full", | |
| paddingValue && `p-[${paddingValue}]`, | |
| className, | |
| ); | |
| const normalClasses = clsx( | |
| baseClasses, | |
| style.background && `bg-[${style.background}]`, | |
| style.border && `border-[${style.border}]`, | |
| style.boxShadow && `shadow-[${style.boxShadow}]`, | |
| ); | |
| // Type-safe hover class construction | |
| const hoverTransform = hoverable && style.hover ? getHoverValue<string>(style, 'transform') : undefined; | |
| const hoverShadow = hoverable && style.hover ? getHoverValue<string>(style, 'boxShadow') : undefined; | |
| const hoverBg = hoverable && style.hover ? getHoverValue<string>(style, 'background') : undefined; | |
| const hoverBorder = hoverable && style.hover ? getHoverValue<string>(style, 'border') : undefined; | |
| const hoverClasses = clsx( | |
| hoverable && "hover:scale-[1.01]", | |
| hoverable && hoverTransform && `hover:${hoverTransform}`, | |
| hoverable && hoverShadow && `hover:shadow-[${hoverShadow}]`, | |
| hoverable && hoverBg && `hover:bg-[${hoverBg}]`, | |
| hoverable && hoverBorder && `hover:border-[${hoverBorder}]`, | |
| ); | |
| const allClasses = clsx(normalClasses, hoverClasses); | |
| // Decide if we need a Link or a div | |
| if (href) { | |
| const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => { | |
| onClick?.(e as unknown as React.MouseEvent<HTMLDivElement | HTMLAnchorElement>); | |
| }; | |
| return ( | |
| <Link | |
| href={href} | |
| ref={ref as unknown as React.RefObject<HTMLAnchorElement>} | |
| className={clsx(allClasses, "cursor-pointer")} | |
| onClick={handleClick} | |
| // Note: We intentionally do NOT spread props here as they come from | |
| // HTMLDivElement which is incompatible with HTMLAnchorElement. | |
| // CardProps extends HTMLAttributes<HTMLDivElement>, but Link | |
| // expects HTMLAnchorElement props. This is a known TypeScript limitation. | |
| // Users should pass Link-specific props via dedicated props, not spread. | |
| > | |
| {children} | |
| </Link> | |
| ); | |
| } | |
| return ( | |
| <div | |
| ref={ref as React.RefObject<HTMLDivElement>} | |
| className={clsx(allClasses, onClick && "cursor-pointer")} | |
| onClick={onClick} | |
| // Only spread props when rendering as div (HTMLDivElement) | |
| {...props} | |
| > | |
| {children} | |
| </div> | |
| ); | |
| }, | |
| ); | |
| SharedCard.displayName = "SharedCard"; | |
| // ============================================================================= | |
| // Named exports for specific card types (for convenience) | |
| // ============================================================================= | |
| // Primary Card (matching /home PrimaryCard) | |
| export function PrimaryCard({ | |
| href, | |
| icon: Icon, | |
| label, | |
| description, | |
| accentClass = "bg-[var(--accent-primary)]/10 text-[var(--accent-primary)]", | |
| borderClass = "border-[var(--accent-primary)]/20", | |
| bgClass = "bg-gradient-to-br from-[var(--accent-primary)]/5 to-transparent", | |
| className, | |
| ...props | |
| }: { | |
| href: string; | |
| icon: React.ElementType; | |
| label: string; | |
| description: string; | |
| accentClass?: string; | |
| borderClass?: string; | |
| bgClass?: string; | |
| className?: string; | |
| } & Omit<CardProps, "variant" | "href">) { | |
| return ( | |
| <SharedCard | |
| variant="primary" | |
| href={href} | |
| className={clsx( | |
| "group flex flex-col", | |
| borderClass, | |
| bgClass, | |
| className, | |
| )} | |
| {...props} | |
| > | |
| <div className={clsx("mb-5 grid size-12 place-items-center rounded-xl", accentClass)}> | |
| <Icon className="size-6" aria-hidden="true" /> | |
| </div> | |
| <h3 className="text-[18px] font-bold text-[var(--text-primary)]">{label}</h3> | |
| <p className="mt-2 flex-1 text-[14px] leading-6 text-[var(--text-secondary)]"> | |
| {description} | |
| </p> | |
| <span className="mt-5 text-[13px] font-semibold text-[var(--accent-primary)] transition group-hover:underline"> | |
| Get started → | |
| </span> | |
| </SharedCard> | |
| ); | |
| } | |
| // Secondary Card (matching /home SecondaryCard) | |
| export function SecondaryCard({ | |
| href, | |
| icon: Icon, | |
| label, | |
| description, | |
| disabled = false, | |
| className, | |
| onClick, | |
| ...props | |
| }: { | |
| href?: string; | |
| icon: React.ElementType; | |
| label: string; | |
| description: string; | |
| disabled?: boolean; | |
| className?: string; | |
| onClick?: () => void; | |
| } & Omit<CardProps, "onClick" | "href">) { | |
| const baseClasses = | |
| "flex items-center gap-3.5 rounded-lg border border-[var(--border-subtle)] bg-[var(--bg-glass)] p-4 transition hover:border-[var(--border-medium)] hover:bg-[var(--bg-elevated)]"; | |
| const inner = ( | |
| <> | |
| <div className="grid size-9 shrink-0 place-items-center rounded-md bg-[var(--bg-elevated)]"> | |
| <Icon className="size-4 text-[var(--text-muted)]" aria-hidden="true" /> | |
| </div> | |
| <div className="min-w-0"> | |
| <p className="text-[13px] font-semibold text-[var(--text-primary)]"> | |
| {label} | |
| {disabled ? ( | |
| <span className="ml-1.5 text-[10px] font-bold uppercase text-[var(--text-muted)]"> | |
| Unavailable | |
| </span> | |
| ) : null} | |
| </p> | |
| <p className="truncate text-[12px] text-[var(--text-muted)]">{description}</p> | |
| </div> | |
| </> | |
| ); | |
| if (disabled) { | |
| return ( | |
| <div | |
| className="flex cursor-default items-center gap-3.5 rounded-lg border border-[var(--border-subtle)] bg-[var(--bg-glass)] p-4 opacity-50" | |
| onClick={onClick} | |
| {...props} | |
| > | |
| {inner} | |
| </div> | |
| ); | |
| } | |
| if (!href) { | |
| return ( | |
| <div | |
| className={clsx(baseClasses, className)} | |
| onClick={onClick} | |
| {...props} | |
| > | |
| {inner} | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <Link href={href} className={clsx(baseClasses, className)}> | |
| {inner} | |
| </Link> | |
| ); | |
| } | |