mirror of
https://github.com/walkxcode/dashboard-icons.git
synced 2025-06-28 07:20:21 +08:00
refactor(web): Update descriptions and consistency (#1264)
* refactor(web): Update descriptions and consistency * revert: Issue templates * refactor(web): More extensive search placeholder --------- Signed-off-by: Bjorn Lammers <bjorn@lammers.media> Co-authored-by: Thomas Camlong <thomas@ajnart.fr>
This commit is contained in:
parent
25f545d632
commit
1bba45f6b4
@ -32,16 +32,16 @@ export default function ErrorPage({
|
|||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl font-bold">Something went wrong</h1>
|
<h1 className="text-2xl font-bold">Something went wrong</h1>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
An unexpected error occurred while loading this page. We've been notified and are looking into it.
|
Unable to load this page. We're looking into the issue.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center pt-4">
|
<div className="flex flex-col sm:flex-row gap-4 justify-center pt-4">
|
||||||
<Button variant="outline" onClick={() => reset()} className="cursor-pointer">
|
<Button variant="outline" onClick={() => reset()} className="cursor-pointer">
|
||||||
<RefreshCcw className="mr-2 h-4 w-4" />
|
<RefreshCcw className="mr-2 h-4 w-4" />
|
||||||
Try again
|
Retry
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleGoBack} className="cursor-pointer">
|
<Button onClick={handleGoBack} className="cursor-pointer">
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
Go back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{error.digest && <p className="text-xs text-muted-foreground mt-6">Error ID: {error.digest}</p>}
|
{error.digest && <p className="text-xs text-muted-foreground mt-6">Error ID: {error.digest}</p>}
|
||||||
|
@ -228,11 +228,11 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
const getSortLabel = (sort: SortOption) => {
|
const getSortLabel = (sort: SortOption) => {
|
||||||
switch (sort) {
|
switch (sort) {
|
||||||
case "relevance":
|
case "relevance":
|
||||||
return "Best match"
|
return "Relevance"
|
||||||
case "alphabetical-asc":
|
case "alphabetical-asc":
|
||||||
return "A to Z"
|
return "Name (A-Z)"
|
||||||
case "alphabetical-desc":
|
case "alphabetical-desc":
|
||||||
return "Z to A"
|
return "Name (Z-A)"
|
||||||
case "newest":
|
case "newest":
|
||||||
return "Newest first"
|
return "Newest first"
|
||||||
default:
|
default:
|
||||||
@ -265,7 +265,7 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
type="search"
|
type="search"
|
||||||
placeholder="Search icons by name, alias, or category..."
|
placeholder="Search for icons..."
|
||||||
className="w-full h-10 pl-9 cursor-text transition-all duration-300 text-sm md:text-base border-border shadow-sm"
|
className="w-full h-10 pl-9 cursor-text transition-all duration-300 text-sm md:text-base border-border shadow-sm"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => handleSearch(e.target.value)}
|
onChange={(e) => handleSearch(e.target.value)}
|
||||||
@ -277,18 +277,18 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
{/* Filter dropdown */}
|
{/* Filter dropdown */}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="outline" size="sm" className="flex-1 sm:flex-none cursor-pointer bg-background border-border shadow-sm ">
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="flex-1 sm:flex-none cursor-pointer bg-background border-border shadow-sm"
|
||||||
|
aria-label="Filter icons"
|
||||||
|
>
|
||||||
<Filter className="h-4 w-4 mr-2" />
|
<Filter className="h-4 w-4 mr-2" />
|
||||||
<span>Filter</span>
|
<span>{selectedCategories.length > 0 ? `Filters (${selectedCategories.length})` : "Filter"}</span>
|
||||||
{selectedCategories.length > 0 && (
|
|
||||||
<Badge variant="secondary" className="ml-2 px-1.5">
|
|
||||||
{selectedCategories.length}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</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">
|
||||||
@ -314,7 +314,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>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -330,18 +330,18 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" className="w-56">
|
<DropdownMenuContent align="start" className="w-56">
|
||||||
<DropdownMenuLabel className="font-semibold">Sort By</DropdownMenuLabel>
|
<DropdownMenuLabel className="font-semibold">Sort Icons</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<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" />
|
||||||
@ -353,9 +353,15 @@ export function IconSearch({ icons }: IconSearchProps) {
|
|||||||
|
|
||||||
{/* Clear all button */}
|
{/* Clear all button */}
|
||||||
{(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"
|
||||||
|
aria-label="Reset all filters"
|
||||||
|
>
|
||||||
<X className="h-4 w-4 mr-2" />
|
<X className="h-4 w-4 mr-2" />
|
||||||
<span>Clear all</span>
|
<span>Reset</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -363,7 +369,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">
|
||||||
@ -389,7 +395,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>
|
||||||
)}
|
)}
|
||||||
@ -400,27 +406,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>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@ -462,7 +474,7 @@ function IconCard({
|
|||||||
className="object-contain p-1 group-hover:scale-110 transition-transform duration-300"
|
className="object-contain p-1 group-hover:scale-110 transition-transform duration-300"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs sm:text-sm text-center truncate w-full capitalize group- dark:group-hover:text-rose-400 transition-colors duration-200 font-medium">
|
<span className="text-xs sm:text-sm text-center truncate w-full capitalize group-hover:text-rose-500 dark:group-hover:text-rose-400 transition-colors duration-200 font-medium">
|
||||||
{name.replace(/-/g, " ")}
|
{name.replace(/-/g, " ")}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
@ -15,9 +15,9 @@ export default function NotFound({
|
|||||||
<div className="mx-auto w-16 h-16 bg-red-100 dark:bg-red-900/20 rounded-full flex items-center justify-center text-red-600 dark:text-red-400">
|
<div className="mx-auto w-16 h-16 bg-red-100 dark:bg-red-900/20 rounded-full flex items-center justify-center text-red-600 dark:text-red-400">
|
||||||
<AlertTriangle className="w-8 h-8" />
|
<AlertTriangle className="w-8 h-8" />
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl sm:text-3xl font-bold mt-6">Icon not found</h1>
|
<h1 className="text-2xl sm:text-3xl font-bold mt-6">Not found</h1>
|
||||||
<p className="text-muted-foreground mt-3 max-w-md">
|
<p className="text-muted-foreground mt-3 max-w-md">
|
||||||
The icon you are looking for could not be found or there was an error loading it.
|
This icon does not exist or could not be loaded.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -25,16 +25,16 @@ export default function NotFound({
|
|||||||
<Button asChild variant="outline">
|
<Button asChild variant="outline">
|
||||||
<Link href="/icons">
|
<Link href="/icons">
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
Back to all icons
|
Back to icons
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-border pt-8 mt-8">
|
<div className="border-t border-border pt-8 mt-8">
|
||||||
<div className="text-center mb-6">
|
<div className="text-center mb-6">
|
||||||
<h2 className="text-xl font-semibold">Can't find what you're looking for?</h2>
|
<h2 className="text-xl font-semibold">Missing an icon?</h2>
|
||||||
<p className="text-muted-foreground mt-2">
|
<p className="text-muted-foreground mt-2">
|
||||||
Contribute to our icon collection by suggesting a new icon or improving an existing one.
|
Submit a new icon or suggest improvements to our collection.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ export function Footer() {
|
|||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<h3 className="font-bold text-lg text-foreground/90">Dashboard Icons</h3>
|
<h3 className="font-bold text-lg text-foreground/90">Dashboard Icons</h3>
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
A collection of curated icons for services, applications and tools, designed specifically for dashboards and app directories.
|
Collection of icons for applications, services, and tools - designed for dashboards and app directories.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -205,13 +205,61 @@ export function HeroSection({ totalIcons, stars }: { totalIcons: number; stars:
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative z-10 container mx-auto px-4 md:px-6 mt-4 py-20">
|
<div className="relative z-10 container mx-auto px-4 sm:px-6 lg:px-8 mt-4 py-20">
|
||||||
<div className="max-w-4xl mx-auto text-center flex flex-col gap-4 ">
|
<div className="max-w-4xl mx-auto text-center flex flex-col gap-4 ">
|
||||||
<h1 className="relative text-3xl sm:text-5xl md:text-7xl font-bold mb-4 md:mb-8 tracking-tight motion-preset-slide-up motion-duration-500 ">
|
<h1 className="relative text-3xl sm:text-5xl md:text-7xl font-bold mb-4 md:mb-8 tracking-tight motion-preset-slide-up motion-duration-500 ">
|
||||||
Your definitive source for
|
Your definitive source for
|
||||||
<Sparkles className="absolute -right-1 -bottom-3 text-rose-500 h-8 w-8 sm:h-12 sm:w-12 md:h-16 md:w-12 motion-delay-300 motion-preset-seesaw-lg motion-scale-in-[0.5] motion-translate-x-in-[-120%] motion-translate-y-in-[-60%] motion-opacity-in-[33%] motion-rotate-in-[-1080deg] motion-blur-in-[10px] motion-duration-500 motion-delay-[0.13s]/scale motion-duration-[0.13s]/opacity motion-duration-[0.40s]/rotate motion-duration-[0.05s]/blur motion-delay-[0.20s]/blur motion-ease-spring-bouncier" />
|
<motion.span
|
||||||
|
className="absolute -right-1 -bottom-3"
|
||||||
|
initial={{ opacity: 0, scale: 0.5, x: -20, y: -10 }}
|
||||||
|
animate={{ opacity: 1, scale: 1, x: 0, y: 0 }}
|
||||||
|
transition={{
|
||||||
|
duration: 0.5,
|
||||||
|
delay: 0.3,
|
||||||
|
ease: "easeOut"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
animate={{
|
||||||
|
y: [0, -3, 0],
|
||||||
|
rotate: [0, 5, 0]
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 3,
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatType: "reverse",
|
||||||
|
ease: "easeInOut"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Sparkles className="text-rose-500 h-8 w-8 sm:h-12 sm:w-12 md:h-16 md:w-12" />
|
||||||
|
</motion.div>
|
||||||
|
</motion.span>
|
||||||
<br />
|
<br />
|
||||||
<Sparkles className="absolute -left-1 -top-3 text-rose-500 h-5 w-5 sm:h-8 sm:w-8 md:h-12 md:w-12 motion-delay-300 motion-preset-seesaw-lg motion-scale-in-[0.5] motion-translate-x-in-[159%] motion-translate-y-in-[-60%] motion-opacity-in-[33%] motion-rotate-in-[-1080deg] motion-blur-in-[10px] motion-duration-500 motion-delay-[0.13s]/scale motion-duration-[0.13s]/opacity motion-duration-[0.40s]/rotate motion-duration-[0.05s]/blur motion-delay-[0.20s]/blur motion-ease-spring-bouncier" />
|
<motion.span
|
||||||
|
className="absolute -left-1 -top-3"
|
||||||
|
initial={{ opacity: 0, scale: 0.5, x: 20, y: -10 }}
|
||||||
|
animate={{ opacity: 1, scale: 1, x: 0, y: 0 }}
|
||||||
|
transition={{
|
||||||
|
duration: 0.5,
|
||||||
|
delay: 0.3,
|
||||||
|
ease: "easeOut"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
animate={{
|
||||||
|
y: [0, -3, 0],
|
||||||
|
rotate: [0, -5, 0]
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 4,
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatType: "reverse",
|
||||||
|
ease: "easeInOut"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Sparkles className="text-rose-500 h-5 w-5 sm:h-8 sm:w-8 md:h-12 md:w-12" />
|
||||||
|
</motion.div>
|
||||||
|
</motion.span>
|
||||||
<AuroraText colors={["#FA5352", "#FA5352", "orange"]}>dashboard icons</AuroraText>
|
<AuroraText colors={["#FA5352", "#FA5352", "orange"]}>dashboard icons</AuroraText>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@ -224,7 +272,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 />
|
||||||
@ -449,12 +497,12 @@ export function GiveUsMoneyButton() {
|
|||||||
<div className="flex justify-between items-center pt-2">
|
<div className="flex justify-between items-center pt-2">
|
||||||
<Link href={openCollectiveUrl} target="_blank" rel="noopener noreferrer">
|
<Link href={openCollectiveUrl} target="_blank" rel="noopener noreferrer">
|
||||||
<Button variant="default" size="sm" className="bg-primary hover:bg-primary/90">
|
<Button variant="default" size="sm" className="bg-primary hover:bg-primary/90">
|
||||||
Donate
|
Support
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={`${openCollectiveUrl}/transactions`} target="_blank" rel="noopener noreferrer">
|
<Link href={`${openCollectiveUrl}/transactions`} target="_blank" rel="noopener noreferrer">
|
||||||
<Button variant="link" size="sm" className="flex items-center gap-1 text-xs text-secondary-foreground">
|
<Button variant="link" size="sm" className="flex items-center gap-1 text-xs text-secondary-foreground">
|
||||||
View expenses
|
View transactions
|
||||||
<ExternalLink className="h-3 w-3" />
|
<ExternalLink className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
@ -478,7 +526,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)}
|
||||||
|
@ -207,6 +207,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
size="icon"
|
size="icon"
|
||||||
className="h-8 w-8 rounded-lg cursor-pointer"
|
className="h-8 w-8 rounded-lg cursor-pointer"
|
||||||
onClick={(e) => handleDownload(e, imageUrl, `${iconName}.${format}`)}
|
onClick={(e) => handleDownload(e, imageUrl, `${iconName}.${format}`)}
|
||||||
|
aria-label={`Download ${iconName} in ${format} format${theme ? ` (${theme} theme)` : ""}`}
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4" />
|
<Download className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -223,6 +224,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
size="icon"
|
size="icon"
|
||||||
className="h-8 w-8 rounded-lg cursor-pointer"
|
className="h-8 w-8 rounded-lg cursor-pointer"
|
||||||
onClick={(e) => handleCopy(imageUrl, `btn-${variantKey}`, e)}
|
onClick={(e) => handleCopy(imageUrl, `btn-${variantKey}`, e)}
|
||||||
|
aria-label={`Copy URL for ${iconName} in ${format} format${theme ? ` (${theme} theme)` : ""}`}
|
||||||
>
|
>
|
||||||
{copiedVariants[`btn-${variantKey}`] ? <Check className="w-4 h-4 text-green-500" /> : <Copy className="w-4 h-4" />}
|
{copiedVariants[`btn-${variantKey}`] ? <Check className="w-4 h-4 text-green-500" /> : <Copy className="w-4 h-4" />}
|
||||||
</Button>
|
</Button>
|
||||||
@ -234,8 +236,18 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
|
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button variant="outline" size="icon" className="h-8 w-8 rounded-lg" asChild>
|
<Button
|
||||||
<Link href={githubUrl} target="_blank" rel="noopener noreferrer">
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8 rounded-lg"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href={githubUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label={`View ${iconName} ${format} file on GitHub`}
|
||||||
|
>
|
||||||
<Github className="w-4 h-4" />
|
<Github className="w-4 h-4" />
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
@ -252,7 +264,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto pt-12 pb-14">
|
<div className="container mx-auto pt-12 pb-14 px-4 sm:px-6 lg:px-8">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||||||
{/* Left Column: Icon Info and Author */}
|
{/* Left Column: Icon Info and Author */}
|
||||||
<div className="lg:col-span-1">
|
<div className="lg:col-span-1">
|
||||||
@ -306,7 +318,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
|
|
||||||
{iconData.categories && iconData.categories.length > 0 && (
|
{iconData.categories && iconData.categories.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Categories</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground mb-2">Categories</h3>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{iconData.categories.map((category) => (
|
{iconData.categories.map((category) => (
|
||||||
<Link key={category} href={`/icons?category=${encodeURIComponent(category)}`} className="cursor-pointer">
|
<Link key={category} href={`/icons?category=${encodeURIComponent(category)}`} className="cursor-pointer">
|
||||||
@ -327,7 +339,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
|
|
||||||
{iconData.aliases && iconData.aliases.length > 0 && (
|
{iconData.aliases && iconData.aliases.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Aliases</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground mb-2">Aliases</h3>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{iconData.aliases.map((alias) => (
|
{iconData.aliases.map((alias) => (
|
||||||
<Badge
|
<Badge
|
||||||
@ -344,19 +356,17 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">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{" "}
|
Available in {availableFormats.length > 1
|
||||||
{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>
|
||||||
Use the {icon} icon in your web applications, dashboards, or documentation to enhance visual communication and user
|
Perfect for adding to dashboards, app directories, documentation, or anywhere you need the {icon.replace(/-/g, " ")} logo.
|
||||||
experience.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -412,7 +422,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="">
|
<div className="">
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Base format</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground mb-2">Base format</h3>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FileType className="w-4 h-4 text-blue-500" />
|
<FileType className="w-4 h-4 text-blue-500" />
|
||||||
<div className="px-3 py-1.5 border border-border rounded-lg text-sm font-medium">{iconData.base.toUpperCase()}</div>
|
<div className="px-3 py-1.5 border border-border rounded-lg text-sm font-medium">{iconData.base.toUpperCase()}</div>
|
||||||
@ -420,7 +430,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="">
|
<div className="">
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Available formats</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground mb-2">Available formats</h3>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{availableFormats.map((format) => (
|
{availableFormats.map((format) => (
|
||||||
<div key={format} className="px-3 py-1.5 border border-border rounded-lg text-xs font-medium">
|
<div key={format} className="px-3 py-1.5 border border-border rounded-lg text-xs font-medium">
|
||||||
@ -432,7 +442,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
|
|
||||||
{iconData.colors && (
|
{iconData.colors && (
|
||||||
<div className="">
|
<div className="">
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Color variants</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground mb-2">Color variants</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{Object.entries(iconData.colors).map(([theme, variant]) => (
|
{Object.entries(iconData.colors).map(([theme, variant]) => (
|
||||||
<div key={theme} className="flex items-center gap-2">
|
<div key={theme} className="flex items-center gap-2">
|
||||||
@ -446,7 +456,7 @@ export function IconDetails({ icon, iconData, authorData }: IconDetailsProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="">
|
<div className="">
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Source</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground mb-2">Source</h3>
|
||||||
<Button variant="outline" className="w-full" asChild>
|
<Button variant="outline" className="w-full" asChild>
|
||||||
<Link href={`${REPO_PATH}/blob/main/meta/${icon}.json`} target="_blank" rel="noopener noreferrer">
|
<Link href={`${REPO_PATH}/blob/main/meta/${icon}.json`} target="_blank" rel="noopener noreferrer">
|
||||||
<Github className="w-4 h-4 mr-2" />
|
<Github className="w-4 h-4 mr-2" />
|
||||||
|
@ -73,13 +73,13 @@ export function IconSubmissionForm() {
|
|||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="outline" className="hidden md:inline-flex cursor-pointer transition-all duration-300">
|
<Button variant="outline" className="hidden md:inline-flex cursor-pointer transition-all duration-300">
|
||||||
<PlusCircle className="h-4 w-4 transition-all duration-300" /> Contribute new icon
|
<PlusCircle className="h-4 w-4 transition-all duration-300" /> Submit icon(s)
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="md:max-w-4xl backdrop-blur-2xl bg-background">
|
<DialogContent className="md:max-w-4xl backdrop-blur-2xl bg-background">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Contribute a new icon</DialogTitle>
|
<DialogTitle>Submit an icon</DialogTitle>
|
||||||
<DialogDescription>Choose a template below to suggest a new icon or improve an existing one.</DialogDescription>
|
<DialogDescription>Select an option below to submit or update an icon.</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<IconSubmissionContent onClose={() => setOpen(false)} />
|
<IconSubmissionContent onClose={() => setOpen(false)} />
|
||||||
|
@ -30,7 +30,7 @@ export function RecentlyAddedIcons({ icons }: { icons: IconWithName[] }) {
|
|||||||
{/* Background glow */}
|
{/* Background glow */}
|
||||||
<div className="absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80" aria-hidden="true" />
|
<div className="absolute inset-x-0 -top-40 -z-10 transform-gpu overflow-hidden blur-3xl sm:-top-80" aria-hidden="true" />
|
||||||
|
|
||||||
<div className="mx-auto px-6 lg:px-8">
|
<div className="mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div className="mx-auto max-w-2xl text-center my-4">
|
<div className="mx-auto max-w-2xl text-center my-4">
|
||||||
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl bg-clip-text text-transparent bg-gradient-to-r from-rose-600 to-rose-500 motion-safe:motion-preset-fade-lg motion-duration-500">
|
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl bg-clip-text text-transparent bg-gradient-to-r from-rose-600 to-rose-500 motion-safe:motion-preset-fade-lg motion-duration-500">
|
||||||
Recently Added Icons
|
Recently Added Icons
|
||||||
@ -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