mirror of
				https://github.com/walkxcode/dashboard-icons.git
				synced 2025-10-31 08:47:57 +01:00 
			
		
		
		
	feat(web): Implement virtualized icon grid
This commit is contained in:
		
				
					committed by
					
						 Bjorn Lammers
						Bjorn Lammers
					
				
			
			
				
	
			
			
			
						parent
						
							3499605fb7
						
					
				
				
					commit
					f3829f533b
				
			| @@ -16,7 +16,7 @@ export function IconCard({ | ||||
| 	return ( | ||||
| 		<MagicCard className="rounded-md shadow-md"> | ||||
| 			<Link prefetch={false} href={`/icons/${name}`} className="group flex flex-col items-center p-3 sm:p-4 cursor-pointer"> | ||||
| 				<div className="relative h-12 w-12 sm:h-16 sm:w-16 mb-2"> | ||||
| 				<div className="relative h-16 w-16 mb-2"> | ||||
| 					<Image | ||||
| 						src={`${BASE_URL}/${iconData.base}/${name}.${iconData.base}`} | ||||
| 						alt={`${name} icon`} | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| import type { Icon } from "@/types/icons" | ||||
|  | ||||
| import { useWindowVirtualizer } from "@tanstack/react-virtual" | ||||
| import { useEffect, useMemo, useRef, useState } from "react" | ||||
| import { IconCard } from "./icon-card" | ||||
|  | ||||
| interface IconsGridProps { | ||||
| @@ -16,3 +18,68 @@ export function IconsGrid({ filteredIcons, matchedAliases }: IconsGridProps) { | ||||
| 		</div> | ||||
| 	) | ||||
| } | ||||
|  | ||||
| export function VirtualizedIconsGrid({ filteredIcons, matchedAliases }: IconsGridProps) { | ||||
| 	const listRef = useRef<HTMLDivElement | null>(null) | ||||
| 	const [windowWidth, setWindowWidth] = useState(typeof window !== "undefined" ? window.innerWidth : 1280) | ||||
|  | ||||
| 	useEffect(() => { | ||||
| 		const handleResize = () => { | ||||
| 			setWindowWidth(window.innerWidth) | ||||
| 		} | ||||
| 		window.addEventListener("resize", handleResize) | ||||
| 		return () => window.removeEventListener("resize", handleResize) | ||||
| 	}, []) | ||||
|  | ||||
| 	const columnCount = useMemo(() => { | ||||
| 		if (windowWidth >= 1280) return 8 // xl | ||||
| 		if (windowWidth >= 1024) return 6 // lg | ||||
| 		if (windowWidth >= 768) return 4 // md | ||||
| 		if (windowWidth >= 640) return 3 // sm | ||||
| 		return 2 // default | ||||
| 	}, [windowWidth]) | ||||
|  | ||||
| 	const rowCount = Math.ceil(filteredIcons.length / columnCount) | ||||
| 	const rowVirtualizer = useWindowVirtualizer({ | ||||
| 		count: rowCount, | ||||
| 		estimateSize: () => 140, | ||||
| 		overscan: 5, | ||||
| 	}) | ||||
|  | ||||
| 	return ( | ||||
| 		<div ref={listRef} className="mt-2"> | ||||
| 			<div | ||||
| 				style={{ | ||||
| 					height: `${rowVirtualizer.getTotalSize()}px`, | ||||
| 					width: "100%", | ||||
| 					position: "relative", | ||||
| 				}} | ||||
| 			> | ||||
| 				{rowVirtualizer.getVirtualItems().map((virtualRow) => { | ||||
| 					const rowStart = virtualRow.index * columnCount | ||||
| 					const rowEnd = Math.min(rowStart + columnCount, filteredIcons.length) | ||||
| 					const rowIcons = filteredIcons.slice(rowStart, rowEnd) | ||||
|  | ||||
| 					return ( | ||||
| 						<div | ||||
| 							key={virtualRow.key} | ||||
| 							style={{ | ||||
| 								position: "absolute", | ||||
| 								top: 0, | ||||
| 								left: 0, | ||||
| 								minHeight: 124, | ||||
| 								width: "100%", | ||||
| 								transform: `translateY(${virtualRow.start - rowVirtualizer.options.scrollMargin}px)`, | ||||
| 							}} | ||||
| 							className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-4" | ||||
| 						> | ||||
| 							{rowIcons.map(({ name, data }) => ( | ||||
| 								<IconCard key={name} name={name} data={data} matchedAlias={matchedAliases[name]} /> | ||||
| 							))} | ||||
| 						</div> | ||||
| 					) | ||||
| 				})} | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	) | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| "use client" | ||||
|  | ||||
| import { IconsGrid } from "@/components/icon-grid" | ||||
| import { VirtualizedIconsGrid } from "@/components/icon-grid" | ||||
| import { IconSubmissionContent } from "@/components/icon-submission-form" | ||||
| import { Badge } from "@/components/ui/badge" | ||||
| import { Button } from "@/components/ui/button" | ||||
| @@ -432,7 +432,7 @@ export function IconSearch({ icons }: IconSearchProps) { | ||||
| 						</div> | ||||
| 					</div> | ||||
|  | ||||
| 					<IconsGrid filteredIcons={filteredIcons} matchedAliases={matchedAliases} /> | ||||
| 					<VirtualizedIconsGrid filteredIcons={filteredIcons} matchedAliases={matchedAliases} /> | ||||
| 				</> | ||||
| 			)} | ||||
| 		</> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user