Download src/components/shared/SharedPill.tsx from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 8.31 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/shared/SharedPill.tsx
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/components/shared/SharedPill.tsx
-
curl -L -o SharedPill.tsx https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/shared/SharedPill.tsx
8.31 kB
| "use client"; | |
| import Link from "next/link"; | |
| import { HTMLAttributes } from "react"; | |
| import { clsx } from "clsx"; | |
| // ============================================================================= | |
| // Pill Types | |
| // ============================================================================= | |
| export interface PillProps extends HTMLAttributes<HTMLSpanElement> { | |
| active?: boolean; | |
| disabled?: boolean; | |
| href?: string; | |
| onClick?: () => void; | |
| onRemove?: () => void; | |
| removable?: boolean; | |
| size?: "xs" | "sm" | "md" | "lg"; | |
| variant?: "primary" | "secondary" | "success" | "warning" | "danger" | "neutral" | "info" | "violet" | "blue" | "amber" | "emerald"; | |
| } | |
| // ============================================================================= | |
| // SharedPill Component | |
| // ============================================================================= | |
| export function SharedPill({ | |
| children, | |
| active = false, | |
| disabled = false, | |
| href, | |
| onClick, | |
| onRemove, | |
| removable = false, | |
| size = "sm", | |
| variant = "secondary", | |
| className, | |
| ...props | |
| }: PillProps) { | |
| // Size classes | |
| const sizeClasses = { | |
| xs: "px-2 py-0.5 text-[10px]", | |
| sm: "px-2.5 py-1 text-[11px]", | |
| md: "px-3 py-1.5 text-[12px]", | |
| lg: "px-4 py-2 text-[13px]", | |
| }; | |
| // Variant classes | |
| const variantClasses = { | |
| primary: { | |
| normal: "bg-[var(--accent-primary)]/10 text-[var(--accent-primary)] border-[var(--accent-primary)]/20", | |
| active: "bg-[var(--accent-primary)]/20 text-[var(--accent-primary)] border-[var(--accent-primary)]/30", | |
| }, | |
| secondary: { | |
| normal: "bg-[var(--bg-elevated)] text-[var(--text-secondary)] border-[var(--border-subtle)]", | |
| active: "bg-[var(--bg-glass)] text-[var(--text-primary)] border-[var(--border-medium)]", | |
| }, | |
| success: { | |
| normal: "bg-[var(--accent-emerald)]/10 text-[var(--accent-emerald)] border-[var(--accent-emerald)]/20", | |
| active: "bg-[var(--accent-emerald)]/20 text-[var(--accent-emerald)] border-[var(--accent-emerald)]/30", | |
| }, | |
| warning: { | |
| normal: "bg-[var(--accent-amber)]/10 text-[var(--accent-amber)] border-[var(--accent-amber)]/20", | |
| active: "bg-[var(--accent-amber)]/20 text-[var(--accent-amber)] border-[var(--accent-amber)]/30", | |
| }, | |
| danger: { | |
| normal: "bg-[var(--accent-red)]/10 text-[var(--accent-red)] border-[var(--accent-red)]/20", | |
| active: "bg-[var(--accent-red)]/20 text-[var(--accent-red)] border-[var(--accent-red)]/30", | |
| }, | |
| neutral: { | |
| normal: "bg-[var(--bg-glass)] text-[var(--text-muted)] border-[var(--border-subtle)]", | |
| active: "bg-[var(--bg-elevated)] text-[var(--text-secondary)] border-[var(--border-medium)]", | |
| }, | |
| // Additional variants | |
| info: { | |
| normal: "bg-blue-400/10 text-blue-400 border-blue-400/20", | |
| active: "bg-blue-400/20 text-blue-400 border-blue-400/30", | |
| }, | |
| violet: { | |
| normal: "bg-violet-400/10 text-violet-400 border-violet-400/20", | |
| active: "bg-violet-400/20 text-violet-400 border-violet-400/30", | |
| }, | |
| blue: { | |
| normal: "bg-blue-400/10 text-blue-400 border-blue-400/20", | |
| active: "bg-blue-400/20 text-blue-400 border-blue-400/30", | |
| }, | |
| amber: { | |
| normal: "bg-amber-400/10 text-amber-400 border-amber-400/20", | |
| active: "bg-amber-400/20 text-amber-400 border-amber-400/30", | |
| }, | |
| emerald: { | |
| normal: "bg-emerald-400/10 text-emerald-400 border-emerald-400/20", | |
| active: "bg-emerald-400/20 text-emerald-400 border-emerald-400/30", | |
| }, | |
| }; | |
| const v = variantClasses[variant]; | |
| const stateClasses = active ? v.active : v.normal; | |
| const s = sizeClasses[size]; | |
| // Build the pill content | |
| const pillContent = ( | |
| <span | |
| className={clsx( | |
| "inline-flex items-center gap-1.5", | |
| "rounded-full", | |
| "border", | |
| "transition-colors", | |
| "duration-150", | |
| stateClasses, | |
| s, | |
| disabled && "opacity-50 cursor-not-allowed", | |
| className, | |
| )} | |
| {...props} | |
| > | |
| {children} | |
| {removable && !disabled && ( | |
| <button | |
| type="button" | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| onRemove?.(); | |
| }} | |
| className="flex size-4 shrink-0 items-center justify-center rounded-full hover:bg-[var(--bg-elevated)] transition" | |
| aria-label="Remove" | |
| > | |
| <span className="text-[10px] font-bold" aria-hidden="true"> | |
| × | |
| </span> | |
| </button> | |
| )} | |
| </span> | |
| ); | |
| // Decide how to render | |
| if (href && !disabled) { | |
| return ( | |
| <Link href={href} className="inline-flex"> | |
| {pillContent} | |
| </Link> | |
| ); | |
| } | |
| if (onClick && !disabled) { | |
| return ( | |
| <button | |
| type="button" | |
| onClick={onClick} | |
| className="inline-flex cursor-pointer" | |
| disabled={disabled} | |
| > | |
| {pillContent} | |
| </button> | |
| ); | |
| } | |
| return pillContent; | |
| } | |
| // ============================================================================= | |
| // Convenience Pill Components | |
| // ============================================================================= | |
| // Filter pill (for syllabus filters) | |
| export function FilterPill({ | |
| children, | |
| active, | |
| onClick, | |
| className, | |
| ...props | |
| }: Omit<PillProps, "variant" | "removable">) { | |
| return ( | |
| <SharedPill | |
| active={active} | |
| onClick={onClick} | |
| variant="secondary" | |
| size="sm" | |
| className={clsx("font-medium", className)} | |
| {...props} | |
| > | |
| {children} | |
| </SharedPill> | |
| ); | |
| } | |
| // Topic pill (for chapter topics) | |
| export function TopicPill({ children, className, ...props }: Omit<PillProps, "variant">) { | |
| return ( | |
| <SharedPill | |
| variant="neutral" | |
| size="xs" | |
| className={clsx("font-medium text-[var(--text-ghost)]", className)} | |
| {...props} | |
| > | |
| {children} | |
| </SharedPill> | |
| ); | |
| } | |
| // Priority pill (for exam priority) | |
| export type PriorityLevel = "critical" | "high" | "moderate" | "optional"; | |
| const priorityConfig: Record<PriorityLevel, { variant: PillProps["variant"]; label: string }> = { | |
| critical: { variant: "danger", label: "Exam critical" }, | |
| high: { variant: "warning", label: "High priority" }, | |
| moderate: { variant: "success", label: "Moderate" }, | |
| optional: { variant: "neutral", label: "Optional" }, | |
| }; | |
| export function PriorityPill({ | |
| level, | |
| showLabel = false, | |
| className, | |
| ...props | |
| }: { level: PriorityLevel; showLabel?: boolean } & Omit<PillProps, "variant" | "children">) { | |
| const config = priorityConfig[level]; | |
| return ( | |
| <SharedPill | |
| variant={config.variant} | |
| size="xs" | |
| className={className} | |
| {...props} | |
| > | |
| {showLabel ? config.label : level} | |
| </SharedPill> | |
| ); | |
| } | |
| // Status pill | |
| export type StatusType = "completed" | "in-progress" | "not-started" | "weak"; | |
| const statusConfig: Record<StatusType, { variant: PillProps["variant"]; label: string }> = { | |
| completed: { variant: "success", label: "Completed" }, | |
| "in-progress": { variant: "warning", label: "In Progress" }, | |
| "not-started": { variant: "neutral", label: "Not Started" }, | |
| weak: { variant: "danger", label: "Weak" }, | |
| }; | |
| export function StatusPill({ | |
| status, | |
| showLabel = false, | |
| className, | |
| ...props | |
| }: { status: StatusType; showLabel?: boolean } & Omit<PillProps, "variant" | "children">) { | |
| const config = statusConfig[status]; | |
| return ( | |
| <SharedPill | |
| variant={config.variant} | |
| size="xs" | |
| className={className} | |
| {...props} | |
| > | |
| {showLabel ? config.label : status} | |
| </SharedPill> | |
| ); | |
| } | |
| // Confidence pill (for teach page) | |
| export type ConfidenceLevel = "high" | "medium" | "low" | "none"; | |
| const confidenceConfig: Record<ConfidenceLevel, { variant: PillProps["variant"]; label: string }> = { | |
| high: { variant: "success", label: "High confidence" }, | |
| medium: { variant: "warning", label: "Likely match" }, | |
| low: { variant: "danger", label: "Best guess" }, | |
| none: { variant: "neutral", label: "No clear match" }, | |
| }; | |
| export function ConfidencePill({ | |
| level, | |
| className, | |
| ...props | |
| }: { level: ConfidenceLevel } & Omit<PillProps, "variant" | "children">) { | |
| const config = confidenceConfig[level]; | |
| return ( | |
| <SharedPill | |
| variant={config.variant} | |
| size="xs" | |
| className={clsx("font-semibold", className)} | |
| {...props} | |
| > | |
| {config.label} | |
| </SharedPill> | |
| ); | |
| } | |