mirror of
				https://github.com/walkxcode/dashboard-icons.git
				synced 2025-11-03 18:18:56 +01:00 
			
		
		
		
	refactor(web): Remove unused components and hooks (#1263)
* Update add_normal_icon.yml Signed-off-by: Thomas Camlong <thomas@ajnart.fr> * Update add_normal_icon.yml Signed-off-by: Thomas Camlong <thomas@ajnart.fr> * Update add_normal_icon.yml Signed-off-by: Thomas Camlong <thomas@ajnart.fr> * change id * refactor(web): Remove unused components and hooks --------- Signed-off-by: Thomas Camlong <thomas@ajnart.fr> Co-authored-by: Thomas Camlong <thomas@ajnart.fr>
This commit is contained in:
		@@ -118,19 +118,6 @@
 | 
			
		||||
			transform: rotate(-5deg) scale(0.9);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	--animate-shiny-text: shiny-text 8s infinite;
 | 
			
		||||
	@keyframes shiny-text {
 | 
			
		||||
		0%,
 | 
			
		||||
		90%,
 | 
			
		||||
		100% {
 | 
			
		||||
			background-position: calc(-100% - var(--shiny-width)) 0;
 | 
			
		||||
		}
 | 
			
		||||
		30%,
 | 
			
		||||
		60% {
 | 
			
		||||
			background-position: calc(100% + var(--shiny-width)) 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
@@ -199,7 +186,7 @@
 | 
			
		||||
	--secondary: oklch(0.31 0.03 266.71);
 | 
			
		||||
	--secondary-foreground: oklch(0.92 0 0);
 | 
			
		||||
	--muted: oklch(0.31 0.03 266.71);
 | 
			
		||||
	--muted-foreground: oklch(0.72 0 0);
 | 
			
		||||
	--muted-foreground: oklch(0.78 0 0);
 | 
			
		||||
	--accent: oklch(0.34 0.06 267.59);
 | 
			
		||||
	--accent-foreground: oklch(0.88 0.06 254.13);
 | 
			
		||||
	--destructive: oklch(0.64 0.21 25.33);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,87 +0,0 @@
 | 
			
		||||
"use client"
 | 
			
		||||
 | 
			
		||||
import { Input } from "@/components/ui/input"
 | 
			
		||||
import { BASE_URL } from "@/constants"
 | 
			
		||||
import type { IconSearchProps, IconWithName } from "@/types/icons"
 | 
			
		||||
import { Search } from "lucide-react"
 | 
			
		||||
import Image from "next/image"
 | 
			
		||||
import Link from "next/link"
 | 
			
		||||
import { useState } from "react"
 | 
			
		||||
 | 
			
		||||
export function IconSearch({ icons, initialQuery = "" }: IconSearchProps) {
 | 
			
		||||
	const [searchQuery, setSearchQuery] = useState(initialQuery)
 | 
			
		||||
	const [filteredIcons, setFilteredIcons] = useState<IconWithName[]>(() => {
 | 
			
		||||
		if (!initialQuery.trim()) return icons
 | 
			
		||||
 | 
			
		||||
		const q = initialQuery.toLowerCase()
 | 
			
		||||
		return icons.filter(({ name, data }) => {
 | 
			
		||||
			if (name.toLowerCase().includes(q)) return true
 | 
			
		||||
			if (data.aliases.some((alias) => alias.toLowerCase().includes(q))) return true
 | 
			
		||||
			if (data.categories.some((category) => category.toLowerCase().includes(q))) return true
 | 
			
		||||
 | 
			
		||||
			return false
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	const handleSearch = (query: string) => {
 | 
			
		||||
		setSearchQuery(query)
 | 
			
		||||
 | 
			
		||||
		if (!query.trim()) {
 | 
			
		||||
			setFilteredIcons(icons)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const q = query.toLowerCase()
 | 
			
		||||
		const filtered = icons.filter(({ name, data }) => {
 | 
			
		||||
			if (name.toLowerCase().includes(q)) return true
 | 
			
		||||
			if (data.aliases.some((alias) => alias.toLowerCase().includes(q))) return true
 | 
			
		||||
			if (data.categories.some((category) => category.toLowerCase().includes(q))) return true
 | 
			
		||||
 | 
			
		||||
			return false
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		setFilteredIcons(filtered)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<>
 | 
			
		||||
			<div className="relative w-full max-w-md">
 | 
			
		||||
				<Search className="absolute left-2.5 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground transition-all duration-300" />
 | 
			
		||||
				<Input
 | 
			
		||||
					type="search"
 | 
			
		||||
					placeholder="Search icons by name, aliases, or categories..."
 | 
			
		||||
					className="w-full pl-8 transition-all duration-300 text-sm md:text-base"
 | 
			
		||||
					value={searchQuery}
 | 
			
		||||
					onChange={(e) => handleSearch(e.target.value)}
 | 
			
		||||
				/>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			{filteredIcons.length === 0 ? (
 | 
			
		||||
				<div className="text-center py-12">
 | 
			
		||||
					<h2 className="text-xl font-semibold">No icons found</h2>
 | 
			
		||||
					<p className="text-muted-foreground mt-2">Try a different search term.</p>
 | 
			
		||||
				</div>
 | 
			
		||||
			) : (
 | 
			
		||||
				<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-4 mt-8">
 | 
			
		||||
					{filteredIcons.map(({ name, data }) => (
 | 
			
		||||
						<Link
 | 
			
		||||
							key={name}
 | 
			
		||||
							href={`/icons/${name}`}
 | 
			
		||||
							className="group flex flex-col items-center p-4 rounded-lg border border-border hover:border-primary hover:bg-accent transition-colors"
 | 
			
		||||
						>
 | 
			
		||||
							<div className="relative h-16 w-16 mb-2">
 | 
			
		||||
								<Image
 | 
			
		||||
									src={`${BASE_URL}/${data.base}/${name}.${data.base}`}
 | 
			
		||||
									alt={`${name} icon`}
 | 
			
		||||
									fill
 | 
			
		||||
									className="object-contain p-1 group-hover:scale-110 transition-transform"
 | 
			
		||||
								/>
 | 
			
		||||
							</div>
 | 
			
		||||
							<span className="text-sm text-center truncate w-full">{name.replace(/-/g, " ")}</span>
 | 
			
		||||
						</Link>
 | 
			
		||||
					))}
 | 
			
		||||
				</div>
 | 
			
		||||
			)}
 | 
			
		||||
		</>
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,138 +0,0 @@
 | 
			
		||||
"use client"
 | 
			
		||||
 | 
			
		||||
import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"
 | 
			
		||||
import { useMediaQuery } from "@/hooks/use-media-query"
 | 
			
		||||
import { fuzzySearch } from "@/lib/utils"
 | 
			
		||||
import { Icon } from "@/types/icons"
 | 
			
		||||
import { useRouter } from "next/navigation"
 | 
			
		||||
import { useCallback, useEffect, useState } from "react"
 | 
			
		||||
 | 
			
		||||
interface CommandMenuProps {
 | 
			
		||||
	icons: {
 | 
			
		||||
		name: string
 | 
			
		||||
		data: {
 | 
			
		||||
			categories: string[]
 | 
			
		||||
			aliases: string[]
 | 
			
		||||
			[key: string]: unknown
 | 
			
		||||
		}
 | 
			
		||||
	}[]
 | 
			
		||||
	triggerButtonId?: string
 | 
			
		||||
	open?: boolean
 | 
			
		||||
	onOpenChange?: (open: boolean) => void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function CommandMenu({ icons, open: externalOpen, onOpenChange: externalOnOpenChange }: CommandMenuProps) {
 | 
			
		||||
	const router = useRouter()
 | 
			
		||||
	const [internalOpen, setInternalOpen] = useState(false)
 | 
			
		||||
	const [query, setQuery] = useState("")
 | 
			
		||||
	const isDesktop = useMediaQuery("(min-width: 768px)")
 | 
			
		||||
 | 
			
		||||
	// Use either external or internal state for controlling open state
 | 
			
		||||
	const isOpen = externalOpen !== undefined ? externalOpen : internalOpen
 | 
			
		||||
 | 
			
		||||
	// Wrap setIsOpen in useCallback to fix dependency issue
 | 
			
		||||
	const setIsOpen = useCallback(
 | 
			
		||||
		(value: boolean) => {
 | 
			
		||||
			if (externalOnOpenChange) {
 | 
			
		||||
				externalOnOpenChange(value)
 | 
			
		||||
			} else {
 | 
			
		||||
				setInternalOpen(value)
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		[externalOnOpenChange],
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	const filteredIcons = getFilteredIcons(icons, query)
 | 
			
		||||
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		const handleKeyDown = (e: KeyboardEvent) => {
 | 
			
		||||
			if (
 | 
			
		||||
				(e.key === "k" && (e.metaKey || e.ctrlKey)) ||
 | 
			
		||||
				(e.key === "/" && document.activeElement?.tagName !== "INPUT" && document.activeElement?.tagName !== "TEXTAREA")
 | 
			
		||||
			) {
 | 
			
		||||
				e.preventDefault()
 | 
			
		||||
				setIsOpen(!isOpen)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		document.addEventListener("keydown", handleKeyDown)
 | 
			
		||||
		return () => document.removeEventListener("keydown", handleKeyDown)
 | 
			
		||||
	}, [isOpen, setIsOpen])
 | 
			
		||||
 | 
			
		||||
	function getFilteredIcons(iconList: CommandMenuProps["icons"], query: string) {
 | 
			
		||||
		if (!query) {
 | 
			
		||||
			// Return a limited number of icons when no query is provided
 | 
			
		||||
			return iconList.slice(0, 8)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Calculate scores for each icon
 | 
			
		||||
		const scoredIcons = iconList.map((icon) => {
 | 
			
		||||
			// Calculate scores for different fields
 | 
			
		||||
			const nameScore = fuzzySearch(icon.name, query) * 2.0 // Give more weight to name matches
 | 
			
		||||
 | 
			
		||||
			// Get max score from aliases
 | 
			
		||||
			const aliasScore =
 | 
			
		||||
				icon.data.aliases && icon.data.aliases.length > 0
 | 
			
		||||
					? Math.max(...icon.data.aliases.map((alias) => fuzzySearch(alias, query))) * 1.8 // Increased weight for aliases
 | 
			
		||||
					: 0
 | 
			
		||||
 | 
			
		||||
			// Get max score from categories
 | 
			
		||||
			const categoryScore =
 | 
			
		||||
				icon.data.categories && icon.data.categories.length > 0
 | 
			
		||||
					? Math.max(...icon.data.categories.map((category) => fuzzySearch(category, query)))
 | 
			
		||||
					: 0
 | 
			
		||||
 | 
			
		||||
			// Use the highest score
 | 
			
		||||
			const score = Math.max(nameScore, aliasScore, categoryScore)
 | 
			
		||||
 | 
			
		||||
			return { icon, score, matchedField: score === nameScore ? "name" : score === aliasScore ? "alias" : "category" }
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// Filter icons with a minimum score and sort by highest score
 | 
			
		||||
		return scoredIcons
 | 
			
		||||
			.filter((item) => item.score > 0.3) // Higher threshold for more accurate results
 | 
			
		||||
			.sort((a, b) => b.score - a.score)
 | 
			
		||||
			.slice(0, 20) // Limit the number of results
 | 
			
		||||
			.map((item) => item.icon)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const handleSelect = (name: string) => {
 | 
			
		||||
		setIsOpen(false)
 | 
			
		||||
		router.push(`/icons/${name}`)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<CommandDialog open={isOpen} onOpenChange={setIsOpen}>
 | 
			
		||||
			<CommandInput placeholder="Search for icons by name, category, or purpose..." value={query} onValueChange={setQuery} />
 | 
			
		||||
			<CommandList>
 | 
			
		||||
				<CommandEmpty>No matching icons found. Try a different search term or browse all icons.</CommandEmpty>
 | 
			
		||||
				<CommandGroup heading="Icons">
 | 
			
		||||
					{filteredIcons.map(({ name, data }) => {
 | 
			
		||||
						// Find matched alias for display if available
 | 
			
		||||
						const matchedAlias =
 | 
			
		||||
							query && data.aliases && data.aliases.length > 0
 | 
			
		||||
								? data.aliases.find((alias) => alias.toLowerCase().includes(query.toLowerCase()))
 | 
			
		||||
								: null
 | 
			
		||||
 | 
			
		||||
						return (
 | 
			
		||||
							<CommandItem key={name} value={name} onSelect={() => handleSelect(name)} className="flex items-center gap-2 cursor-pointer">
 | 
			
		||||
								<div className="flex-shrink-0 h-5 w-5 relative">
 | 
			
		||||
									<div className="h-5 w-5 bg-rose-100 dark:bg-rose-900/30 rounded-md flex items-center justify-center">
 | 
			
		||||
										<span className="text-[10px] font-medium text-rose-800 dark:text-rose-300">{name.substring(0, 2).toUpperCase()}</span>
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
								<span className="flex-grow capitalize">{name.replace(/-/g, " ")}</span>
 | 
			
		||||
								{matchedAlias && <span className="text-xs text-primary-500 truncate max-w-[100px]">alias: {matchedAlias}</span>}
 | 
			
		||||
								{!matchedAlias && data.categories && data.categories.length > 0 && (
 | 
			
		||||
									<span className="text-xs text-muted-foreground truncate max-w-[100px]">
 | 
			
		||||
										{data.categories[0].replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())}
 | 
			
		||||
									</span>
 | 
			
		||||
								)}
 | 
			
		||||
							</CommandItem>
 | 
			
		||||
						)
 | 
			
		||||
					})}
 | 
			
		||||
				</CommandGroup>
 | 
			
		||||
			</CommandList>
 | 
			
		||||
		</CommandDialog>
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
@@ -3,42 +3,14 @@
 | 
			
		||||
import { IconSubmissionForm } from "@/components/icon-submission-form"
 | 
			
		||||
import { ThemeSwitcher } from "@/components/theme-switcher"
 | 
			
		||||
import { REPO_PATH } from "@/constants"
 | 
			
		||||
import { getIconsArray } from "@/lib/api"
 | 
			
		||||
import type { IconWithName } from "@/types/icons"
 | 
			
		||||
import { motion } from "framer-motion"
 | 
			
		||||
import { Github, Search } from "lucide-react"
 | 
			
		||||
import { Github } from "lucide-react"
 | 
			
		||||
import Link from "next/link"
 | 
			
		||||
import { useEffect, useState } from "react"
 | 
			
		||||
import { CommandMenu } from "./command-menu"
 | 
			
		||||
import { HeaderNav } from "./header-nav"
 | 
			
		||||
import { Button } from "./ui/button"
 | 
			
		||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip"
 | 
			
		||||
 | 
			
		||||
export function Header() {
 | 
			
		||||
	const [iconsData, setIconsData] = useState<IconWithName[]>([])
 | 
			
		||||
	const [isLoaded, setIsLoaded] = useState(false)
 | 
			
		||||
	const [commandMenuOpen, setCommandMenuOpen] = useState(false)
 | 
			
		||||
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		async function loadIcons() {
 | 
			
		||||
			try {
 | 
			
		||||
				const icons = await getIconsArray()
 | 
			
		||||
				setIconsData(icons)
 | 
			
		||||
				setIsLoaded(true)
 | 
			
		||||
			} catch (error) {
 | 
			
		||||
				console.error("Failed to load icons:", error)
 | 
			
		||||
				setIsLoaded(true)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		loadIcons()
 | 
			
		||||
	}, [])
 | 
			
		||||
 | 
			
		||||
	// Function to open the command menu
 | 
			
		||||
	const openCommandMenu = () => {
 | 
			
		||||
		setCommandMenuOpen(true)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<motion.header
 | 
			
		||||
			className="border-b sticky top-0 z-50 backdrop-blur-2xl bg-background/50 border-border/50"
 | 
			
		||||
@@ -56,30 +28,6 @@ export function Header() {
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div className="flex items-center gap-2 md:gap-4">
 | 
			
		||||
					{/* Desktop search button */}
 | 
			
		||||
					<div className="hidden md:block">
 | 
			
		||||
						<Button variant="outline" className="gap-2 cursor-pointer   transition-all duration-300" onClick={openCommandMenu}>
 | 
			
		||||
							<Search className="h-4 w-4 transition-all duration-300" />
 | 
			
		||||
							<span>Find icons</span>
 | 
			
		||||
							<kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-border/80 bg-muted/80 px-1.5 font-mono text-[10px] font-medium opacity-100">
 | 
			
		||||
								<span className="text-xs">⌘</span>K
 | 
			
		||||
							</kbd>
 | 
			
		||||
						</Button>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					{/* Mobile search button */}
 | 
			
		||||
					<div className="md:hidden">
 | 
			
		||||
						<Button
 | 
			
		||||
							variant="ghost"
 | 
			
		||||
							size="icon"
 | 
			
		||||
							className="rounded-lg cursor-pointer transition-all duration-300 hover:ring-2 "
 | 
			
		||||
							onClick={openCommandMenu}
 | 
			
		||||
						>
 | 
			
		||||
							<Search className="h-5 w-5 transition-all duration-300" />
 | 
			
		||||
							<span className="sr-only">Find icons</span>
 | 
			
		||||
						</Button>
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
					<div className="hidden md:flex items-center gap-2 md:gap-4">
 | 
			
		||||
						<IconSubmissionForm />
 | 
			
		||||
						<TooltipProvider>
 | 
			
		||||
@@ -106,9 +54,6 @@ export function Header() {
 | 
			
		||||
					<ThemeSwitcher />
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			{/* Single instance of CommandMenu */}
 | 
			
		||||
			{isLoaded && <CommandMenu icons={iconsData} open={commandMenuOpen} onOpenChange={setCommandMenuOpen} />}
 | 
			
		||||
		</motion.header>
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +0,0 @@
 | 
			
		||||
import type { CSSProperties, ComponentPropsWithoutRef, FC } from "react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
export interface AnimatedShinyTextProps extends ComponentPropsWithoutRef<"span"> {
 | 
			
		||||
	shimmerWidth?: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AnimatedShinyText: FC<AnimatedShinyTextProps> = ({ children, className, shimmerWidth = 100, ...props }) => {
 | 
			
		||||
	return (
 | 
			
		||||
		<span
 | 
			
		||||
			style={
 | 
			
		||||
				{
 | 
			
		||||
					"--shiny-width": `${shimmerWidth}px`,
 | 
			
		||||
				} as CSSProperties
 | 
			
		||||
			}
 | 
			
		||||
			className={cn(
 | 
			
		||||
				"mx-auto max-w-md text-neutral-600/70 dark:text-neutral-400/70",
 | 
			
		||||
 | 
			
		||||
				// Shine effect
 | 
			
		||||
				"animate-shiny-text bg-clip-text bg-no-repeat [background-position:0_0] [background-size:var(--shiny-width)_100%] [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]",
 | 
			
		||||
 | 
			
		||||
				// Shine gradient
 | 
			
		||||
				"bg-gradient-to-r from-transparent via-black/80 via-50% to-transparent  dark:via-white/80",
 | 
			
		||||
 | 
			
		||||
				className,
 | 
			
		||||
			)}
 | 
			
		||||
			{...props}
 | 
			
		||||
		>
 | 
			
		||||
			{children}
 | 
			
		||||
		</span>
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,177 +0,0 @@
 | 
			
		||||
"use client"
 | 
			
		||||
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import { Command as CommandPrimitive } from "cmdk"
 | 
			
		||||
import { SearchIcon } from "lucide-react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
import {
 | 
			
		||||
  Dialog,
 | 
			
		||||
  DialogContent,
 | 
			
		||||
  DialogDescription,
 | 
			
		||||
  DialogHeader,
 | 
			
		||||
  DialogTitle,
 | 
			
		||||
} from "@/components/ui/dialog"
 | 
			
		||||
 | 
			
		||||
function Command({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<typeof CommandPrimitive>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <CommandPrimitive
 | 
			
		||||
      data-slot="command"
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function CommandDialog({
 | 
			
		||||
  title = "Command Palette",
 | 
			
		||||
  description = "Search for a command to run...",
 | 
			
		||||
  children,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<typeof Dialog> & {
 | 
			
		||||
  title?: string
 | 
			
		||||
  description?: string
 | 
			
		||||
}) {
 | 
			
		||||
  return (
 | 
			
		||||
    <Dialog {...props}>
 | 
			
		||||
      <DialogHeader className="sr-only">
 | 
			
		||||
        <DialogTitle>{title}</DialogTitle>
 | 
			
		||||
        <DialogDescription>{description}</DialogDescription>
 | 
			
		||||
      </DialogHeader>
 | 
			
		||||
      <DialogContent className="overflow-hidden p-0">
 | 
			
		||||
        <Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
 | 
			
		||||
          {children}
 | 
			
		||||
        </Command>
 | 
			
		||||
      </DialogContent>
 | 
			
		||||
    </Dialog>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function CommandInput({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      data-slot="command-input-wrapper"
 | 
			
		||||
      className="flex h-9 items-center gap-2 border-b px-3"
 | 
			
		||||
    >
 | 
			
		||||
      <SearchIcon className="size-4 shrink-0 opacity-50" />
 | 
			
		||||
      <CommandPrimitive.Input
 | 
			
		||||
        data-slot="command-input"
 | 
			
		||||
        className={cn(
 | 
			
		||||
          "placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
 | 
			
		||||
          className
 | 
			
		||||
        )}
 | 
			
		||||
        {...props}
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function CommandList({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<typeof CommandPrimitive.List>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <CommandPrimitive.List
 | 
			
		||||
      data-slot="command-list"
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function CommandEmpty({
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <CommandPrimitive.Empty
 | 
			
		||||
      data-slot="command-empty"
 | 
			
		||||
      className="py-6 text-center text-sm"
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function CommandGroup({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <CommandPrimitive.Group
 | 
			
		||||
      data-slot="command-group"
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function CommandSeparator({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <CommandPrimitive.Separator
 | 
			
		||||
      data-slot="command-separator"
 | 
			
		||||
      className={cn("bg-border -mx-1 h-px", className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function CommandItem({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <CommandPrimitive.Item
 | 
			
		||||
      data-slot="command-item"
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function CommandShortcut({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<"span">) {
 | 
			
		||||
  return (
 | 
			
		||||
    <span
 | 
			
		||||
      data-slot="command-shortcut"
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "text-muted-foreground ml-auto text-xs tracking-widest",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  Command,
 | 
			
		||||
  CommandDialog,
 | 
			
		||||
  CommandInput,
 | 
			
		||||
  CommandList,
 | 
			
		||||
  CommandEmpty,
 | 
			
		||||
  CommandGroup,
 | 
			
		||||
  CommandItem,
 | 
			
		||||
  CommandShortcut,
 | 
			
		||||
  CommandSeparator,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,25 +0,0 @@
 | 
			
		||||
"use client"
 | 
			
		||||
 | 
			
		||||
import { useEffect, useState } from "react"
 | 
			
		||||
 | 
			
		||||
export function useMediaQuery(query: string): boolean {
 | 
			
		||||
	const [matches, setMatches] = useState(false)
 | 
			
		||||
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		const media = window.matchMedia(query)
 | 
			
		||||
 | 
			
		||||
		// Initial check
 | 
			
		||||
		if (media.matches !== matches) {
 | 
			
		||||
			setMatches(media.matches)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Setup listener for changes
 | 
			
		||||
		const listener = () => setMatches(media.matches)
 | 
			
		||||
		media.addEventListener("change", listener)
 | 
			
		||||
 | 
			
		||||
		// Cleanup
 | 
			
		||||
		return () => media.removeEventListener("change", listener)
 | 
			
		||||
	}, [query, matches])
 | 
			
		||||
 | 
			
		||||
	return matches
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user