{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "repository-card-glass", "type": "registry:block", "title": "Repository Card Glass", "description": "ContributionBadge - Shows contribution percentage with color-coded variant", "dependencies": [ "react" ], "registryDependencies": [ "cn" ], "files": [ { "path": "components/glass/composite/repository-card-glass.tsx", "type": "registry:component", "content": "/* eslint-disable react-refresh/only-export-components */\n// ========================================\n// REPOSITORY CARD GLASS COMPONENT\n// Expandable repository card with compound API\n// Issue #15: shadcn/ui compatible compound component\n// ========================================\n\nimport { forwardRef, type CSSProperties, type ReactNode, useMemo } from 'react';\nimport {\n ChevronDown,\n ChevronUp,\n Star,\n ExternalLink,\n Sparkles,\n AlertTriangle,\n GitFork,\n Activity,\n Shield,\n} from 'lucide-react';\nimport { cn } from '@/lib/utils';\nimport { StatusIndicatorGlass } from '../../specialized/status-indicator-glass';\nimport { ButtonGlass } from '../../ui/button-glass';\nimport { BadgeGlass } from '../../ui/badge-glass';\nimport { AvatarGlass, AvatarGlassImage, AvatarGlassFallback } from '../../ui/avatar-glass';\nimport { ProgressGlass } from '../../specialized/progress-glass';\nimport {\n HoverCardGlass,\n HoverCardGlassTrigger,\n HoverCardGlassContent,\n} from '../../ui/hover-card-glass';\nimport { InteractiveCard } from '../../primitives';\nimport {\n RepositoryCardContext,\n useRepositoryCard,\n type RepositoryCardContextValue,\n} from './repository-card-context';\nimport {\n getContributionBadgeVariant,\n getActivityStatus,\n getActivityStatusInfo,\n getRoleBadgeVariant,\n getRoleLabel,\n getHealthStatus,\n getHealthStatusInfo,\n getLanguageColor,\n calculateContributionPercent,\n formatNumberWithSuffix,\n type ContributorRole,\n type TeamMember,\n} from './repository-card-utils';\nimport '@/glass-theme.css';\n\n// ========================================\n// TYPES\n// ========================================\n\nexport type RepositoryFlagType = 'green' | 'yellow' | 'red';\n\n/** Props for RepositoryCardGlass.Root */\nexport interface RepositoryCardRootProps extends React.HTMLAttributes {\n /** Whether the card is expanded */\n expanded?: boolean;\n /** Callback when toggle is triggered */\n onToggle?: () => void;\n children: ReactNode;\n}\n\n/** Props for RepositoryCardGlass.Header */\nexport interface RepositoryCardHeaderProps extends React.HTMLAttributes {\n children: ReactNode;\n}\n\n/** Props for RepositoryCardGlass.Name */\nexport interface RepositoryCardNameProps extends React.HTMLAttributes {\n children: ReactNode;\n}\n\n/** Props for RepositoryCardGlass.Status */\nexport interface RepositoryCardStatusProps {\n type?: RepositoryFlagType;\n className?: string;\n}\n\n/** Props for RepositoryCardGlass.Stars */\nexport interface RepositoryCardStarsProps extends React.HTMLAttributes {\n count: number;\n}\n\n/** Props for RepositoryCardGlass.Meta */\nexport interface RepositoryCardMetaProps extends React.HTMLAttributes {\n children: ReactNode;\n}\n\n/** Props for RepositoryCardGlass.Languages */\nexport interface RepositoryCardLanguagesProps extends React.HTMLAttributes {\n children: ReactNode;\n}\n\n/** Props for RepositoryCardGlass.Stats */\nexport interface RepositoryCardStatsProps extends React.HTMLAttributes {\n children: ReactNode;\n}\n\n/** Props for RepositoryCardGlass.ExpandedContent */\nexport interface RepositoryCardExpandedContentProps extends React.HTMLAttributes {\n children: ReactNode;\n}\n\n/** Props for RepositoryCardGlass.Issues */\nexport interface RepositoryCardIssuesProps extends React.HTMLAttributes {\n issues: readonly string[];\n}\n\n/** Props for RepositoryCardGlass.Metrics */\nexport interface RepositoryCardMetricsProps extends React.HTMLAttributes {\n children: ReactNode;\n}\n\n/** Props for RepositoryCardGlass.MetricItem */\nexport interface RepositoryCardMetricItemProps extends React.HTMLAttributes {\n label: string;\n value: string | number;\n subtitle?: string;\n}\n\n/** Props for RepositoryCardGlass.Actions */\nexport interface RepositoryCardActionsProps extends React.HTMLAttributes {\n children: ReactNode;\n}\n\n// ========================================\n// NEW SUB-COMPONENT TYPES (Issue #28)\n// ========================================\n\n/** Props for RepositoryCardGlass.ContributionBadge */\nexport interface RepositoryCardContributionBadgeProps {\n /** Contribution percentage (0-100) */\n percent: number;\n /** Optional className */\n className?: string;\n}\n\n/** Props for RepositoryCardGlass.ForkBadge */\nexport interface RepositoryCardForkBadgeProps {\n /** Original repository name (e.g., \"facebook/react\") */\n forkedFrom?: string;\n /** Optional className */\n className?: string;\n}\n\n/** Props for RepositoryCardGlass.Language */\nexport interface RepositoryCardLanguageProps {\n /** Language name */\n name: string;\n /** Custom color (defaults to GitHub language color) */\n color?: string;\n /** Percentage of codebase */\n percent?: number;\n /** Optional className */\n className?: string;\n}\n\n/** Props for RepositoryCardGlass.ActivityStatus */\nexport interface RepositoryCardActivityStatusProps {\n /** Last activity date (ISO string or Date) */\n lastActivityDate: string | Date;\n /** Optional className */\n className?: string;\n}\n\n/** Props for RepositoryCardGlass.RoleBadge */\nexport interface RepositoryCardRoleBadgeProps {\n /** Contributor role */\n role: ContributorRole;\n /** Optional className */\n className?: string;\n}\n\n/** Props for RepositoryCardGlass.TeamAvatars */\nexport interface RepositoryCardTeamAvatarsProps {\n /** Team members */\n members: TeamMember[];\n /** Total team size (for \"+N\" indicator) */\n total?: number;\n /** Max avatars to show */\n max?: number;\n /** Optional className */\n className?: string;\n}\n\n/** Props for RepositoryCardGlass.HealthStatus */\nexport interface RepositoryCardHealthStatusProps {\n /** Whether the repo is archived */\n isArchived?: boolean;\n /** Last push date (ISO string or Date) */\n pushedAt: string | Date;\n /** Optional className */\n className?: string;\n}\n\n/** Props for RepositoryCardGlass.ContributionProgress */\nexport interface RepositoryCardContributionProgressProps {\n /** User's commits */\n userCommits: number;\n /** Total project commits */\n totalCommits: number;\n /** PRs merged by user */\n prsMerged?: number;\n /** Code reviews by user */\n reviews?: number;\n /** Optional className */\n className?: string;\n}\n\n/** Legacy props for backward compatibility */\nexport interface RepositoryCardGlassLegacyProps extends React.HTMLAttributes {\n readonly name: string;\n readonly languages: string;\n readonly commits: number;\n readonly contribution: number;\n readonly stars?: number;\n readonly flagType?: RepositoryFlagType;\n readonly issues?: readonly string[];\n readonly expanded?: boolean;\n readonly onToggle?: () => void;\n readonly onGitHubClick?: () => void;\n readonly onAIAnalysisClick?: () => void;\n}\n\n// ========================================\n// COMPOUND COMPONENTS\n// ========================================\n\n/** Root container with context provider */\nconst RepositoryCardRoot = forwardRef(\n ({ expanded = false, onToggle, children, className, ...props }, ref) => {\n const contextValue = useMemo(\n () => ({ expanded, onToggle }),\n [expanded, onToggle]\n );\n\n return (\n \n \n {children}\n \n \n );\n }\n);\nRepositoryCardRoot.displayName = 'RepositoryCardGlass.Root';\n\n/** Header section with toggle functionality */\nconst RepositoryCardHeader = forwardRef(\n ({ children, className, ...props }, ref) => {\n const { expanded, onToggle } = useRepositoryCard();\n\n return (\n {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onToggle?.();\n }\n }}\n aria-expanded={expanded}\n {...props}\n >\n
\n
{children}
\n
\n {expanded ? (\n \n ) : (\n \n )}\n
\n
\n \n );\n }\n);\nRepositoryCardHeader.displayName = 'RepositoryCardGlass.Header';\n\n/** Repository name */\nconst RepositoryCardName = forwardRef(\n ({ children, className, ...props }, ref) => (\n \n {children}\n \n )\n);\nRepositoryCardName.displayName = 'RepositoryCardGlass.Name';\n\n/** Status indicator wrapper */\nconst RepositoryCardStatus = forwardRef(\n ({ type = 'green', className }, ref) => (\n \n \n \n )\n);\nRepositoryCardStatus.displayName = 'RepositoryCardGlass.Status';\n\n/** Stars count display */\nconst RepositoryCardStars = forwardRef(\n ({ count, className, ...props }, ref) => {\n if (count <= 0) return null;\n\n return (\n \n \n {count}\n \n );\n }\n);\nRepositoryCardStars.displayName = 'RepositoryCardGlass.Stars';\n\n/** Meta information container */\nconst RepositoryCardMeta = forwardRef(\n ({ children, className, ...props }, ref) => (\n
\n {children}\n
\n )\n);\nRepositoryCardMeta.displayName = 'RepositoryCardGlass.Meta';\n\n/** Languages display */\nconst RepositoryCardLanguages = forwardRef(\n ({ children, className, ...props }, ref) => (\n
\n {children}\n
\n )\n);\nRepositoryCardLanguages.displayName = 'RepositoryCardGlass.Languages';\n\n/** Stats line display */\nconst RepositoryCardStatsLine = forwardRef(\n ({ children, className, ...props }, ref) => (\n
\n {children}\n
\n )\n);\nRepositoryCardStatsLine.displayName = 'RepositoryCardGlass.Stats';\n\n/** Expanded content container - only renders when expanded */\nconst RepositoryCardExpandedContent = forwardRef<\n HTMLDivElement,\n RepositoryCardExpandedContentProps\n>(({ children, className, ...props }, ref) => {\n const { expanded } = useRepositoryCard();\n\n if (!expanded) return null;\n\n const expandedStyles: CSSProperties = {\n background: 'var(--expanded-bg)',\n borderColor: 'var(--card-border)',\n };\n\n return (\n \n {children}\n \n );\n});\nRepositoryCardExpandedContent.displayName = 'RepositoryCardGlass.ExpandedContent';\n\n/** Issues alert */\nconst RepositoryCardIssues = forwardRef(\n ({ issues, className, ...props }, ref) => {\n if (issues.length === 0) return null;\n\n return (\n \n \n \n Issues\n \n {issues.map((issue, index) => (\n \n • {issue}\n \n ))}\n \n );\n }\n);\nRepositoryCardIssues.displayName = 'RepositoryCardGlass.Issues';\n\n/** Metrics grid container */\nconst RepositoryCardMetrics = forwardRef(\n ({ children, className, ...props }, ref) => (\n
\n {children}\n
\n )\n);\nRepositoryCardMetrics.displayName = 'RepositoryCardGlass.Metrics';\n\n/** Single metric item */\nconst RepositoryCardMetricItem = forwardRef(\n ({ label, value, subtitle, className, ...props }, ref) => {\n const metricCardStyles: CSSProperties = {\n background: 'var(--card-bg)',\n borderColor: 'var(--card-border)',\n };\n\n return (\n \n
{label}
\n
{value}
\n {subtitle &&
{subtitle}
}\n \n );\n }\n);\nRepositoryCardMetricItem.displayName = 'RepositoryCardGlass.MetricItem';\n\n/** Actions container */\nconst RepositoryCardActions = forwardRef(\n ({ children, className, ...props }, ref) => (\n
\n {children}\n
\n )\n);\nRepositoryCardActions.displayName = 'RepositoryCardGlass.Actions';\n\n// ========================================\n// NEW SUB-COMPONENTS (Issue #28)\n// ========================================\n\n/**\n * ContributionBadge - Shows contribution percentage with color-coded variant\n * 0-25%: destructive, 26-50%: warning, 51-75%: default, 76-100%: success\n */\nconst RepositoryCardContributionBadge = forwardRef<\n HTMLDivElement,\n RepositoryCardContributionBadgeProps\n>(({ percent, className }, ref) => {\n const variant = getContributionBadgeVariant(percent);\n\n return (\n \n {percent}%\n \n );\n});\nRepositoryCardContributionBadge.displayName = 'RepositoryCardGlass.ContributionBadge';\n\n/**\n * ForkBadge - Indicates the repository is a fork\n */\nconst RepositoryCardForkBadge = forwardRef(\n ({ forkedFrom, className }, ref) => {\n if (forkedFrom) {\n return (\n \n \n \n \n \n Fork\n \n \n \n \n

\n Forked from{' '}\n \n {forkedFrom}\n \n

\n
\n
\n );\n }\n\n return (\n \n \n Fork\n \n );\n }\n);\nRepositoryCardForkBadge.displayName = 'RepositoryCardGlass.ForkBadge';\n\n/**\n * Language - Single language with color dot and optional percentage\n */\nconst RepositoryCardLanguage = forwardRef(\n ({ name, color, percent, className }, ref) => {\n const languageColor = getLanguageColor(name, color);\n\n return (\n \n \n {name}\n {percent !== undefined && {percent}%}\n \n );\n }\n);\nRepositoryCardLanguage.displayName = 'RepositoryCardGlass.Language';\n\n/**\n * ActivityStatus - Shows repository activity status based on last activity date\n */\nconst RepositoryCardActivityStatus = forwardRef(\n ({ lastActivityDate, className }, ref) => {\n const status = getActivityStatus(lastActivityDate);\n const { label, variant } = getActivityStatusInfo(status);\n\n return (\n \n \n {label}\n \n );\n }\n);\nRepositoryCardActivityStatus.displayName = 'RepositoryCardGlass.ActivityStatus';\n\n/**\n * RoleBadge - Shows contributor role\n */\nconst RepositoryCardRoleBadge = forwardRef(\n ({ role, className }, ref) => {\n const variant = getRoleBadgeVariant(role);\n const label = getRoleLabel(role);\n\n return (\n \n {label}\n \n );\n }\n);\nRepositoryCardRoleBadge.displayName = 'RepositoryCardGlass.RoleBadge';\n\n/**\n * TeamAvatars - Shows team member avatars with HoverCard previews\n */\nconst RepositoryCardTeamAvatars = forwardRef(\n ({ members, total, max = 5, className }, ref) => {\n const visibleMembers = members.slice(0, max);\n const remaining = total ? total - max : members.length - max;\n\n return (\n
\n {visibleMembers.map((member) => (\n \n \n \n \n {member.avatar ? (\n \n ) : null}\n \n {member.name\n .split(' ')\n .map((n) => n[0])\n .join('')\n .toUpperCase()\n .slice(0, 2)}\n \n \n \n \n \n
\n \n {member.avatar ? (\n \n ) : null}\n \n {member.name\n .split(' ')\n .map((n) => n[0])\n .join('')\n .toUpperCase()\n .slice(0, 2)}\n \n \n
\n

\n {member.name}\n

\n {member.role && (\n

\n {getRoleLabel(member.role)}\n

\n )}\n
\n
\n
\n
\n ))}\n\n {remaining > 0 && (\n \n +{remaining}\n \n )}\n
\n );\n }\n);\nRepositoryCardTeamAvatars.displayName = 'RepositoryCardGlass.TeamAvatars';\n\n/**\n * HealthStatus - Shows repository health based on last push and archive status\n */\nconst RepositoryCardHealthStatus = forwardRef(\n ({ isArchived = false, pushedAt, className }, ref) => {\n const status = getHealthStatus(pushedAt, isArchived);\n const { label, variant } = getHealthStatusInfo(status);\n\n return (\n \n \n {label}\n \n );\n }\n);\nRepositoryCardHealthStatus.displayName = 'RepositoryCardGlass.HealthStatus';\n\n/**\n * ContributionProgress - Shows contribution metrics with progress bars\n */\nconst RepositoryCardContributionProgress = forwardRef<\n HTMLDivElement,\n RepositoryCardContributionProgressProps\n>(({ userCommits, totalCommits, prsMerged, reviews, className }, ref) => {\n const commitPercent = calculateContributionPercent(userCommits, totalCommits);\n\n return (\n
\n {/* Commits progress */}\n
\n
\n Commits\n \n {formatNumberWithSuffix(userCommits)} / {formatNumberWithSuffix(totalCommits)} (\n {commitPercent}%)\n \n
\n \n
\n\n {/* Additional metrics */}\n {(prsMerged !== undefined || reviews !== undefined) && (\n
\n {prsMerged !== undefined && (\n
\n
\n PRs Merged\n
\n
\n {prsMerged}\n
\n
\n )}\n {reviews !== undefined && (\n
\n
\n Reviews\n
\n
\n {reviews}\n
\n
\n )}\n
\n )}\n
\n );\n});\nRepositoryCardContributionProgress.displayName = 'RepositoryCardGlass.ContributionProgress';\n\n// ========================================\n// LEGACY COMPONENT (backward compatibility)\n// ========================================\n\nconst RepositoryCardGlassLegacy = forwardRef(\n (\n {\n name,\n languages,\n commits,\n contribution,\n stars = 0,\n flagType = 'green',\n issues = [],\n expanded = false,\n onToggle,\n onGitHubClick,\n onAIAnalysisClick,\n className,\n ...props\n },\n ref\n ) => {\n // Calculate total project commits from contribution percentage\n const totalProjectCommits =\n contribution > 0 ? Math.round(commits / (contribution / 100)) : commits;\n const estimatedLines = Math.round(commits * 12);\n\n return (\n \n \n {name}\n \n \n \n\n \n {languages}\n \n {commits} commits · {contribution}%\n \n \n\n \n \n\n \n \n \n \n\n \n {\n e.stopPropagation();\n onGitHubClick?.();\n }}\n className=\"flex-1\"\n >\n GitHub\n \n {\n e.stopPropagation();\n onAIAnalysisClick?.();\n }}\n className=\"flex-1\"\n >\n AI Analysis\n \n \n \n \n );\n }\n);\nRepositoryCardGlassLegacy.displayName = 'RepositoryCardGlass';\n\n// ========================================\n// EXPORTS\n// ========================================\n\n/**\n * RepositoryCardGlass - Expandable repository card with compound API\n *\n * @example Legacy API (backward compatible)\n * ```tsx\n * setExpanded(!expanded)}\n * />\n * ```\n *\n * @example Compound API (flexible)\n * ```tsx\n * setExpanded(!expanded)}>\n * \n * my-repo\n * \n * \n * \n * \n * TypeScript\n * 500 commits · 45%\n * \n * \n * \n * \n * \n * \n * \n * ```\n */\nexport const RepositoryCardGlass = Object.assign(RepositoryCardGlassLegacy, {\n Root: RepositoryCardRoot,\n Header: RepositoryCardHeader,\n Name: RepositoryCardName,\n Status: RepositoryCardStatus,\n Stars: RepositoryCardStars,\n Meta: RepositoryCardMeta,\n Languages: RepositoryCardLanguages,\n Stats: RepositoryCardStatsLine,\n ExpandedContent: RepositoryCardExpandedContent,\n Issues: RepositoryCardIssues,\n Metrics: RepositoryCardMetrics,\n MetricItem: RepositoryCardMetricItem,\n Actions: RepositoryCardActions,\n // Issue #28: New sub-components\n ContributionBadge: RepositoryCardContributionBadge,\n ForkBadge: RepositoryCardForkBadge,\n Language: RepositoryCardLanguage,\n ActivityStatus: RepositoryCardActivityStatus,\n RoleBadge: RepositoryCardRoleBadge,\n TeamAvatars: RepositoryCardTeamAvatars,\n HealthStatus: RepositoryCardHealthStatus,\n ContributionProgress: RepositoryCardContributionProgress,\n});\n\n// Named exports for direct imports\nexport {\n RepositoryCardRoot,\n RepositoryCardHeader,\n RepositoryCardName,\n RepositoryCardStatus,\n RepositoryCardStars,\n RepositoryCardMeta,\n RepositoryCardLanguages,\n RepositoryCardStatsLine,\n RepositoryCardExpandedContent,\n RepositoryCardIssues,\n RepositoryCardMetrics,\n RepositoryCardMetricItem,\n RepositoryCardActions,\n // Issue #28: New sub-components\n RepositoryCardContributionBadge,\n RepositoryCardForkBadge,\n RepositoryCardLanguage,\n RepositoryCardActivityStatus,\n RepositoryCardRoleBadge,\n RepositoryCardTeamAvatars,\n RepositoryCardHealthStatus,\n RepositoryCardContributionProgress,\n};\n\n// Re-export context hook\nexport { useRepositoryCard, useRepositoryCardOptional } from './repository-card-context';\n\n// Re-export utility types\nexport type {\n ActivityStatusType,\n ContributorRole,\n HealthStatusType,\n TeamMember,\n} from './repository-card-utils';\n" } ], "categories": [ "composite" ] }