mirror of
https://github.com/walkxcode/dashboard-icons.git
synced 2025-10-28 05:59:04 +08: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:
@@ -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,
|
||||
}
|
||||
Reference in New Issue
Block a user