{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "projects-list-glass", "type": "registry:block", "title": "Projects List Glass", "description": "Projects List Glass component with glass effects", "dependencies": [ "lucide-react", "react" ], "registryDependencies": [ "cn" ], "files": [ { "path": "components/glass/sections/projects-list-glass.tsx", "type": "registry:component", "content": "// ========================================\n// PROJECTS LIST GLASS COMPONENT\n// List of repository cards with filtering, sorting, and ownership controls\n// ========================================\n\nimport { forwardRef, useState, useMemo, type CSSProperties } from \"react\";\nimport { FolderGit2, AlertTriangle } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { GlassCard } from \"../ui/glass-card\";\nimport { BadgeGlass } from \"../ui/badge-glass\";\nimport { RepositoryCardGlass, type RepositoryFlagType } from \"../composite/repository-card-glass\";\nimport { SegmentedControlGlass } from \"../specialized/segmented-control-glass\";\nimport { SortDropdownGlass, type SortField, type SortOrder } from \"../atomic\";\nimport \"@/glass-theme.css\";\n\n// ========================================\n// TYPES\n// ========================================\n\nexport type OwnershipFilter = 'your' | 'contrib' | 'all';\n\n// Re-export for convenience\nexport type { SortField, SortOrder } from \"../atomic\";\n\nexport interface Repository {\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 createdYear?: number;\n /** Ownership type for Your/Contrib filtering */\n readonly ownership?: 'your' | 'contrib';\n}\n\nexport interface ProjectsListGlassProps extends React.HTMLAttributes {\n /** List of repositories to display */\n readonly repositories: readonly Repository[];\n /** Show only flagged (non-green) repositories */\n readonly showFlaggedOnly?: boolean;\n /** Filter by creation year */\n readonly selectedYear?: number | null;\n /** Callback to clear all filters */\n readonly onClearFilters?: () => void;\n /** Custom title (overrides auto-generated) */\n readonly title?: string;\n\n // ======== NEW: Ownership Filter ========\n /** Current ownership filter */\n readonly ownershipFilter?: OwnershipFilter;\n /** Callback when ownership filter changes */\n readonly onOwnershipChange?: (filter: OwnershipFilter) => void;\n\n // ======== NEW: Sorting ========\n /** Current sort field */\n readonly sortBy?: SortField;\n /** Current sort order */\n readonly sortOrder?: SortOrder;\n /** Callback when sort changes */\n readonly onSortChange?: (field: SortField, order: SortOrder) => void;\n\n // ======== NEW: Controls ========\n /** Show header controls (sort dropdown, ownership filter). Default: true when callbacks provided */\n readonly showControls?: boolean;\n}\n\n// ========================================\n// OWNERSHIP FILTER OPTIONS\n// ========================================\n\nconst ownershipOptions = [\n { value: 'your' as const, label: 'Your' },\n { value: 'contrib' as const, label: 'Contrib' },\n];\n\n// ========================================\n// COMPONENT\n// ========================================\n\nexport const ProjectsListGlass = forwardRef(\n (\n {\n repositories,\n showFlaggedOnly = false,\n selectedYear = null,\n onClearFilters,\n title,\n // New props\n ownershipFilter,\n onOwnershipChange,\n sortBy = 'commits',\n sortOrder = 'desc',\n onSortChange,\n showControls,\n className,\n ...props\n },\n ref\n ) => {\n const [expandedIndex, setExpandedIndex] = useState(null);\n\n // Determine if controls should be shown\n const shouldShowControls = showControls ?? (onOwnershipChange !== undefined || onSortChange !== undefined);\n\n // Filter repositories\n const filteredRepos = useMemo(() => {\n return repositories.filter((repo) => {\n // Flag filter\n if (showFlaggedOnly && repo.flagType === \"green\") return false;\n // Year filter\n if (selectedYear && repo.createdYear !== selectedYear) return false;\n // Ownership filter\n if (ownershipFilter && ownershipFilter !== 'all' && repo.ownership !== ownershipFilter) return false;\n return true;\n });\n }, [repositories, showFlaggedOnly, selectedYear, ownershipFilter]);\n\n // Sort repositories\n const sortedRepos = useMemo(() => {\n if (!onSortChange) return filteredRepos;\n\n return [...filteredRepos].sort((a, b) => {\n const multiplier = sortOrder === 'asc' ? 1 : -1;\n switch (sortBy) {\n case 'commits':\n return (a.commits - b.commits) * multiplier;\n case 'stars':\n return ((a.stars ?? 0) - (b.stars ?? 0)) * multiplier;\n case 'name':\n return a.name.localeCompare(b.name) * multiplier;\n case 'contribution':\n return (a.contribution - b.contribution) * multiplier;\n default:\n return 0;\n }\n });\n }, [filteredRepos, sortBy, sortOrder, onSortChange]);\n\n // Generate title based on filters\n const displayTitle = title ?? (\n showFlaggedOnly\n ? \"Flagged\"\n : selectedYear\n ? `${selectedYear}`\n : \"All\"\n ) + \" Projects\";\n\n const emptyContainerStyles: CSSProperties = {\n background: \"var(--card-bg)\",\n };\n\n // Handle ownership change with type safety\n const handleOwnershipChange = (value: string): void => {\n if (onOwnershipChange && (value === 'your' || value === 'contrib')) {\n onOwnershipChange(value);\n }\n };\n\n return (\n \n {/* Header */}\n
\n {/* Left: Title + Badge */}\n
\n {showFlaggedOnly ? (\n \n ) : (\n \n )}\n \n {displayTitle}\n \n {sortedRepos.length} repos\n
\n\n {/* Right: Controls */}\n {shouldShowControls && (\n
\n {onSortChange && (\n \n )}\n {onOwnershipChange && ownershipFilter && (\n \n )}\n
\n )}\n
\n\n {/* Content */}\n {sortedRepos.length === 0 ? (\n // Empty State\n
\n \n \n
\n \n No repositories found\n

\n {onClearFilters && (\n \n Clear filters\n \n )}\n \n ) : (\n // Repository List\n
\n {sortedRepos.map((repo, index) => (\n setExpandedIndex(expandedIndex === index ? null : index)}\n />\n ))}\n
\n )}\n \n );\n }\n);\n\nProjectsListGlass.displayName = \"ProjectsListGlass\";\n" } ], "categories": [ "sections" ] }