mirror of
https://github.com/walkxcode/dashboard-icons.git
synced 2025-06-28 15:30:22 +08:00
refactor(web): Reintroduce specific lost features after d0f8f8c (#1285)
Some checks failed
Trigger Cloudflare Pages Build / cron_job (push) Has been cancelled
Some checks failed
Trigger Cloudflare Pages Build / cron_job (push) Has been cancelled
Co-authored-by: Thomas Camlong <thomas@ajnart.fr>
This commit is contained in:
parent
2d8a8957d4
commit
09a30fd4fa
@ -51,11 +51,7 @@ export async function generateMetadata({ params, searchParams }: Props, parent:
|
|||||||
return {
|
return {
|
||||||
title: `${formattedIconName} Icon | Dashboard Icons`,
|
title: `${formattedIconName} Icon | Dashboard Icons`,
|
||||||
description: `Download the ${formattedIconName} icon in SVG, PNG, and WEBP formats for FREE. Part of a collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`,
|
description: `Download the ${formattedIconName} icon in SVG, PNG, and WEBP formats for FREE. Part of a collection of ${totalIcons} curated icons for services, applications and tools, designed specifically for dashboards and app directories.`,
|
||||||
assets: [
|
assets: [`${BASE_URL}/svg/${icon}.svg`, `${BASE_URL}/png/${icon}.png`, `${BASE_URL}/webp/${icon}.webp`],
|
||||||
`${BASE_URL}/svg/${icon}.svg`,
|
|
||||||
`${BASE_URL}/png/${icon}.png`,
|
|
||||||
`${BASE_URL}/webp/${icon}.webp`,
|
|
||||||
],
|
|
||||||
keywords: [
|
keywords: [
|
||||||
`${formattedIconName} icon`,
|
`${formattedIconName} icon`,
|
||||||
`${formattedIconName} icon download`,
|
`${formattedIconName} icon download`,
|
||||||
|
@ -46,7 +46,7 @@ export default async function IconsPage() {
|
|||||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold">Browse icons</h1>
|
<h1 className="text-3xl font-bold">Browse icons</h1>
|
||||||
<p className="text-muted-foreground">Search through our collection of {icons.length} beautiful icons.</p>
|
<p className="text-muted-foreground mb-1">Search through our collection of {icons.length} beautiful icons.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<IconSearch icons={icons} />
|
<IconSearch icons={icons} />
|
||||||
|
@ -82,7 +82,6 @@ export async function generateMetadata(): Promise<Metadata> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body className={`${inter.variable} antialiased bg-background flex flex-col min-h-screen`}>
|
<body className={`${inter.variable} antialiased bg-background flex flex-col min-h-screen`}>
|
||||||
|
@ -39,9 +39,7 @@ export function Header() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header className="border-b sticky top-0 z-50 backdrop-blur-2xl bg-background/50 border-border/50">
|
||||||
className="border-b sticky top-0 z-50 backdrop-blur-2xl bg-background/50 border-border/50"
|
|
||||||
>
|
|
||||||
<div className="px-4 md:px-12 flex items-center justify-between h-16 md:h-18">
|
<div className="px-4 md:px-12 flex items-center justify-between h-16 md:h-18">
|
||||||
<div className="flex items-center gap-2 md:gap-6">
|
<div className="flex items-center gap-2 md:gap-6">
|
||||||
<Link href="/" className="text-lg md:text-xl font-bold group hidden md:block">
|
<Link href="/" className="text-lg md:text-xl font-bold group hidden md:block">
|
||||||
|
@ -224,7 +224,7 @@ export function HeroSection({ totalIcons, stars }: { totalIcons: number; stars:
|
|||||||
<SearchInput searchQuery={searchQuery} setSearchQuery={setSearchQuery} totalIcons={totalIcons} />
|
<SearchInput searchQuery={searchQuery} setSearchQuery={setSearchQuery} totalIcons={totalIcons} />
|
||||||
<div className="w-full flex gap-3 md:gap-4 flex-wrap justify-center motion-preset-slide-down motion-duration-500">
|
<div className="w-full flex gap-3 md:gap-4 flex-wrap justify-center motion-preset-slide-down motion-duration-500">
|
||||||
<Link href="/icons">
|
<Link href="/icons">
|
||||||
<InteractiveHoverButton className="rounded-md bg-input/30">Explore icons</InteractiveHoverButton>
|
<InteractiveHoverButton className="rounded-md bg-input/30">Browse icons</InteractiveHoverButton>
|
||||||
</Link>
|
</Link>
|
||||||
<GiveUsAStarButton stars={stars} />
|
<GiveUsAStarButton stars={stars} />
|
||||||
<GiveUsMoneyButton />
|
<GiveUsMoneyButton />
|
||||||
@ -478,7 +478,7 @@ function SearchInput({ searchQuery, setSearchQuery, totalIcons }: SearchInputPro
|
|||||||
name="q"
|
name="q"
|
||||||
autoFocus
|
autoFocus
|
||||||
type="search"
|
type="search"
|
||||||
placeholder={`Find any of ${totalIcons} icons by name or category...`}
|
placeholder={`Search our collection of ${totalIcons} icons by name or category...`}
|
||||||
className="pl-10 h-10 md:h-12 rounded-lg w-full border-border focus:border-primary/20 text-sm md:text-base"
|
className="pl-10 h-10 md:h-12 rounded-lg w-full border-border focus:border-primary/20 text-sm md:text-base"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
@ -30,8 +30,6 @@ export function IconCard({
|
|||||||
<span className="text-xs sm:text-sm text-center truncate w-full capitalize group- dark:group-hover:text-primary transition-colors duration-200 font-medium">
|
<span className="text-xs sm:text-sm text-center truncate w-full capitalize group- dark:group-hover:text-primary transition-colors duration-200 font-medium">
|
||||||
{formatedIconName}
|
{formatedIconName}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{matchedAlias && <span className="text-[10px] text-center truncate w-full mt-1">Alias: {matchedAlias}</span>}
|
|
||||||
</Link>
|
</Link>
|
||||||
</MagicCard>
|
</MagicCard>
|
||||||
)
|
)
|
||||||
|
@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button"
|
|||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
||||||
import { BASE_URL, REPO_PATH } from "@/constants"
|
import { BASE_URL, REPO_PATH } from "@/constants"
|
||||||
|
import { formatIconName } from "@/lib/utils"
|
||||||
import type { AuthorData, Icon, IconFile } from "@/types/icons"
|
import type { AuthorData, Icon, IconFile } from "@/types/icons"
|
||||||
import confetti from "canvas-confetti"
|
import confetti from "canvas-confetti"
|
||||||
import { motion } from "framer-motion"
|
import { motion } from "framer-motion"
|
||||||
@ -18,7 +19,6 @@ import { toast } from "sonner"
|
|||||||
import { Carbon } from "./carbon"
|
import { Carbon } from "./carbon"
|
||||||
import { MagicCard } from "./magicui/magic-card"
|
import { MagicCard } from "./magicui/magic-card"
|
||||||
import { Badge } from "./ui/badge"
|
import { Badge } from "./ui/badge"
|
||||||
import { formatIconName } from "@/lib/utils"
|
|
||||||
|
|
||||||
export type IconDetailsProps = {
|
export type IconDetailsProps = {
|
||||||
icon: string
|
icon: string
|
||||||
@ -241,12 +241,7 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
|
|||||||
|
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button variant="outline" size="icon" className="h-8 w-8 rounded-lg" asChild>
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
className="h-8 w-8 rounded-lg"
|
|
||||||
asChild
|
|
||||||
>
|
|
||||||
<Link
|
<Link
|
||||||
href={githubUrl}
|
href={githubUrl}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -369,14 +364,16 @@ export function IconDetails({ icon, iconData, authorData, allIcons }: IconDetail
|
|||||||
<h3 className="text-sm font-semibold text-muted-foreground mb-2">About this icon</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground mb-2">About this icon</h3>
|
||||||
<div className="text-xs text-muted-foreground space-y-2">
|
<div className="text-xs text-muted-foreground space-y-2">
|
||||||
<p>
|
<p>
|
||||||
Available in {availableFormats.length > 1
|
Available in{" "}
|
||||||
|
{availableFormats.length > 1
|
||||||
? `${availableFormats.length} formats (${availableFormats.map((f) => f.toUpperCase()).join(", ")}) `
|
? `${availableFormats.length} formats (${availableFormats.map((f) => f.toUpperCase()).join(", ")}) `
|
||||||
: `${availableFormats[0].toUpperCase()} format `}
|
: `${availableFormats[0].toUpperCase()} format `}
|
||||||
with a base format of {iconData.base.toUpperCase()}.
|
with a base format of {iconData.base.toUpperCase()}.
|
||||||
{iconData.colors && " Includes both light and dark theme variants for better integration with different UI designs."}
|
{iconData.colors && " Includes both light and dark theme variants for better integration with different UI designs."}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Perfect for adding to dashboards, app directories, documentation, or anywhere you need the {icon.replace(/-/g, " ")} logo.
|
Perfect for adding to dashboards, app directories, documentation, or anywhere you need the {formatIconName(icon)}{" "}
|
||||||
|
logo.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -285,7 +285,7 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" className="w-64 sm:w-56">
|
<DropdownMenuContent align="start" className="w-64 sm:w-56">
|
||||||
<DropdownMenuLabel className="font-semibold">Categories</DropdownMenuLabel>
|
<DropdownMenuLabel className="font-semibold">Select Categories</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
<div className="max-h-[40vh] overflow-y-auto p-1">
|
<div className="max-h-[40vh] overflow-y-auto p-1">
|
||||||
@ -311,7 +311,7 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
}}
|
}}
|
||||||
className="cursor-pointer focus: focus:bg-rose-50 dark:focus:bg-rose-950/20"
|
className="cursor-pointer focus: focus:bg-rose-50 dark:focus:bg-rose-950/20"
|
||||||
>
|
>
|
||||||
Clear all filters
|
Clear categories
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -332,13 +332,15 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
<DropdownMenuRadioGroup value={sortOption} onValueChange={(value) => handleSortChange(value as SortOption)}>
|
<DropdownMenuRadioGroup value={sortOption} onValueChange={(value) => handleSortChange(value as SortOption)}>
|
||||||
<DropdownMenuRadioItem value="relevance" className="cursor-pointer">
|
<DropdownMenuRadioItem value="relevance" className="cursor-pointer">
|
||||||
<Search className="h-4 w-4 mr-2" />
|
<Search className="h-4 w-4 mr-2" />
|
||||||
Best match
|
Relevance
|
||||||
</DropdownMenuRadioItem>
|
</DropdownMenuRadioItem>
|
||||||
<DropdownMenuRadioItem value="alphabetical-asc" className="cursor-pointer">
|
<DropdownMenuRadioItem value="alphabetical-asc" className="cursor-pointer">
|
||||||
<ArrowDownAZ className="h-4 w-4 mr-2" />A to Z
|
<ArrowDownAZ className="h-4 w-4 mr-2" />
|
||||||
|
Name (A-Z)
|
||||||
</DropdownMenuRadioItem>
|
</DropdownMenuRadioItem>
|
||||||
<DropdownMenuRadioItem value="alphabetical-desc" className="cursor-pointer">
|
<DropdownMenuRadioItem value="alphabetical-desc" className="cursor-pointer">
|
||||||
<ArrowUpZA className="h-4 w-4 mr-2" />Z to A
|
<ArrowUpZA className="h-4 w-4 mr-2" />
|
||||||
|
Name (Z-A)
|
||||||
</DropdownMenuRadioItem>
|
</DropdownMenuRadioItem>
|
||||||
<DropdownMenuRadioItem value="newest" className="cursor-pointer">
|
<DropdownMenuRadioItem value="newest" className="cursor-pointer">
|
||||||
<Calendar className="h-4 w-4 mr-2" />
|
<Calendar className="h-4 w-4 mr-2" />
|
||||||
@ -352,7 +354,7 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
{(searchQuery || selectedCategories.length > 0 || sortOption !== "relevance") && (
|
{(searchQuery || selectedCategories.length > 0 || sortOption !== "relevance") && (
|
||||||
<Button variant="outline" size="sm" onClick={clearFilters} className="flex-1 sm:flex-none cursor-pointer bg-background">
|
<Button variant="outline" size="sm" onClick={clearFilters} className="flex-1 sm:flex-none cursor-pointer bg-background">
|
||||||
<X className="h-4 w-4 mr-2" />
|
<X className="h-4 w-4 mr-2" />
|
||||||
<span>Clear all</span>
|
<span>Reset all</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -360,7 +362,7 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
{/* Active filter badges */}
|
{/* Active filter badges */}
|
||||||
{selectedCategories.length > 0 && (
|
{selectedCategories.length > 0 && (
|
||||||
<div className="flex flex-wrap items-center gap-2 mt-2">
|
<div className="flex flex-wrap items-center gap-2 mt-2">
|
||||||
<span className="text-sm text-muted-foreground">Filters:</span>
|
<span className="text-sm text-muted-foreground">Selected:</span>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{selectedCategories.map((category) => (
|
{selectedCategories.map((category) => (
|
||||||
<Badge key={category} variant="secondary" className="flex items-center gap-1 pl-2 pr-1">
|
<Badge key={category} variant="secondary" className="flex items-center gap-1 pl-2 pr-1">
|
||||||
@ -386,7 +388,7 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
}}
|
}}
|
||||||
className="text-xs h-7 px-2 cursor-pointer"
|
className="text-xs h-7 px-2 cursor-pointer"
|
||||||
>
|
>
|
||||||
Clear all
|
Clear
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -397,27 +399,33 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
{filteredIcons.length === 0 ? (
|
{filteredIcons.length === 0 ? (
|
||||||
<div className="flex flex-col gap-8 py-12 max-w-2xl mx-auto items-center">
|
<div className="flex flex-col gap-8 py-12 max-w-2xl mx-auto items-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h2 className="text-3xl sm:text-5xl font-semibold">We don't have this one...yet!</h2>
|
<h2 className="text-3xl sm:text-5xl font-semibold">Icon not found</h2>
|
||||||
|
<p className="text-lg text-muted-foreground mt-2">Help us expand our collection</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-4 items-center w-full">
|
||||||
|
<IconSubmissionContent />
|
||||||
|
<div className="mt-4 flex items-center gap-2 justify-center">
|
||||||
|
<span className="text-sm text-muted-foreground">Can't submit it yourself?</span>
|
||||||
|
<Button
|
||||||
|
className="cursor-pointer"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setIsLazyRequestSubmitted(true)
|
||||||
|
toast("Request received!", {
|
||||||
|
description: `We've noted your request for "${searchQuery || "this icon"}". Thanks for your suggestion.`,
|
||||||
|
})
|
||||||
|
posthog.capture("lazy icon request", {
|
||||||
|
query: searchQuery,
|
||||||
|
categories: selectedCategories,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
disabled={isLazyRequestSubmitted}
|
||||||
|
>
|
||||||
|
Request this icon
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
|
||||||
className="cursor-pointer motion-preset-pop"
|
|
||||||
variant="default"
|
|
||||||
size="lg"
|
|
||||||
onClick={() => {
|
|
||||||
setIsLazyRequestSubmitted(true)
|
|
||||||
toast("We hear you!", {
|
|
||||||
description: `Okay, okay... we'll consider adding "${searchQuery || "that icon"}" just for you. 😉`,
|
|
||||||
})
|
|
||||||
posthog.capture("lazy icon request", {
|
|
||||||
query: searchQuery,
|
|
||||||
categories: selectedCategories,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
disabled={isLazyRequestSubmitted}
|
|
||||||
>
|
|
||||||
I want this icon added but I'm too lazy to add it myself
|
|
||||||
</Button>
|
|
||||||
<IconSubmissionContent />
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
@ -61,7 +61,7 @@ export function RecentlyAddedIcons({ icons }: { icons: IconWithName[] }) {
|
|||||||
href="/icons"
|
href="/icons"
|
||||||
className="font-medium inline-flex items-center py-2 px-4 rounded-full border transition-all duration-200 group hover-lift soft-shadow"
|
className="font-medium inline-flex items-center py-2 px-4 rounded-full border transition-all duration-200 group hover-lift soft-shadow"
|
||||||
>
|
>
|
||||||
View complete collection
|
View all icons
|
||||||
<ArrowRight className="w-4 h-4 ml-1.5 transition-transform duration-200 group-hover:translate-x-1" />
|
<ArrowRight className="w-4 h-4 ml-1.5 transition-transform duration-200 group-hover:translate-x-1" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user