feat: add browser search rainbow button and dialog

Co-authored-by: ajnart <49837342+ajnart@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-08 10:05:55 +00:00
parent 990d640872
commit d45a26bea5
4 changed files with 217 additions and 1 deletions

View File

@@ -0,0 +1,152 @@
"use client"
import { useState } from "react"
import { Check, Copy, Globe2, Search, Sparkles } from "lucide-react"
import { RainbowButton } from "@/registry/magicui/rainbow-button"
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator"
import { cn } from "@/lib/utils"
const SEARCH_SCHEMA = "https://dashboardicons.com/icons?q=%s"
const instructions: Array<{ browser: string; steps: string[] }> = [
{
browser: "Chrome",
steps: [
"Open Settings → Search engine → Manage search engines and site search",
"Click Add next to Site search",
"Name: Dashboard Icons · Shortcut: di (or anything you like)",
`URL with %s: ${SEARCH_SCHEMA}`,
"Save, then type your shortcut + Space/Tab in the address bar to search",
],
},
{
browser: "Firefox",
steps: [
"Open Settings → Search",
"Scroll to Search Shortcuts and click Add",
"Name: Dashboard Icons · Keyword: di",
`Search URL: ${SEARCH_SCHEMA}`,
"Use the keyword + Space, then type your query",
],
},
{
browser: "Edge",
steps: [
"Open Settings → Privacy, search, and services",
"Under Address bar and search, click Manage search engines",
"Select Add search engine",
"Name: Dashboard Icons · Shortcut: di · URL: above schema",
"Choose the new entry and Set as default or use via shortcut",
],
},
{
browser: "Safari",
steps: [
"Open Settings → Search and enable “Quick Website Search”",
"Visit dashboardicons.com and run any search once",
"Soon Safari will suggest “dashboardicons” as a quick search option",
"Type dashboardicons + Space in the Smart Search field to search directly",
"If not suggested, add a Shortcut via Settings → Search → Manage Websites",
],
},
]
export function AddToSearchBarButton({
size = "default",
className,
}: {
size?: "sm" | "default" | "lg"
className?: string
}) {
const [copied, setCopied] = useState(false)
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(SEARCH_SCHEMA)
setCopied(true)
setTimeout(() => setCopied(false), 1500)
} catch {
setCopied(false)
}
}
return (
<Dialog>
<DialogTrigger asChild>
<RainbowButton
variant="outline"
size={size}
className={cn(
"shadow-sm px-5 hover:shadow-[0_10px_40px_-20px_rgba(99,102,241,0.6)] active:scale-[0.99]",
className,
)}
>
<Search className="h-4 w-4" />
<span className="hidden sm:inline">Add to browser search</span>
<span className="sm:hidden">Add to search</span>
<Sparkles className="h-4 w-4 text-primary" />
</RainbowButton>
</DialogTrigger>
<DialogContent className="sm:max-w-xl">
<DialogHeader className="space-y-1">
<DialogTitle className="flex items-center gap-2 text-lg sm:text-xl">
<Globe2 className="h-5 w-5 text-primary" />
Add dashboardicons to your browser search bar
</DialogTitle>
<DialogDescription>
Drop this search URL into your browser&apos;s custom search engines so you can type once and jump straight to the right icon.
</DialogDescription>
</DialogHeader>
<div className="space-y-3 rounded-lg border bg-muted/30 p-3">
<p className="text-sm font-medium text-muted-foreground">Search URL (schema)</p>
<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
<Input readOnly value={SEARCH_SCHEMA} className="font-mono text-xs" />
<Button
variant="secondary"
size="sm"
className="flex items-center gap-2"
type="button"
onClick={handleCopy}
>
{copied ? (
<>
<Check className="h-4 w-4" /> Copied
</>
) : (
<>
<Copy className="h-4 w-4" /> Copy
</>
)}
</Button>
</div>
</div>
<Separator />
<div className="grid gap-4 sm:grid-cols-2">
{instructions.map((entry) => (
<div key={entry.browser} className="rounded-lg border bg-card/50 p-3 shadow-[0_5px_30px_-25px_rgba(0,0,0,0.4)]">
<div className="flex items-center justify-between">
<p className="text-sm font-semibold">{entry.browser}</p>
<span className="text-[10px] uppercase tracking-wide text-primary/80">1 minute setup</span>
</div>
<ol className="mt-2 space-y-1 text-xs text-muted-foreground leading-relaxed">
{entry.steps.map((step, idx) => (
<li key={step} className="flex gap-2">
<span className="font-semibold text-primary">{idx + 1}.</span>
<span>{step}</span>
</li>
))}
</ol>
</div>
))}
</div>
</DialogContent>
</Dialog>
)
}

View File

@@ -25,6 +25,7 @@ import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { cn } from "@/lib/utils"
import { AddToSearchBarButton } from "./add-to-search-bar-button"
import { AuroraText } from "./magicui/aurora-text"
import { InteractiveHoverButton } from "./magicui/interactive-hover-button"
import { NumberTicker } from "./magicui/number-ticker"
@@ -233,6 +234,9 @@ export function HeroSection({ totalIcons, stars }: { totalIcons: number; stars:
<GiveUsMoneyButton />
<GiveUsLoveButton />
</div>
<div className="flex justify-center">
<AddToSearchBarButton size="lg" />
</div>
</div>
</div>
</div>

View File

@@ -5,9 +5,9 @@ import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { useTheme } from "next-themes"
import posthog from "posthog-js"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { toast } from "sonner"
import { VirtualizedIconsGrid } from "@/components/icon-grid"
import { IconSubmissionContent } from "@/components/icon-submission-form"
import { AddToSearchBarButton } from "@/components/add-to-search-bar-button"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
@@ -325,6 +325,8 @@ export function IconSearch({ icons }: IconSearchProps) {
</DropdownMenuContent>
</DropdownMenu>
<AddToSearchBarButton size="sm" className="flex-1 sm:flex-none" />
{/* Clear all button */}
{(searchQuery || selectedCategories.length > 0 || sortOption !== "relevance") && (
<Button variant="outline" size="sm" onClick={clearFilters} className="flex-1 sm:flex-none cursor-pointer bg-background">

View File

@@ -0,0 +1,58 @@
"use client"
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const rainbowButtonVariants = cva(
"relative inline-flex items-center justify-center overflow-hidden rounded-full font-semibold transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
{
variants: {
variant: {
default: "text-white shadow-lg",
outline: "text-foreground border border-border bg-background/80 backdrop-blur-sm",
},
size: {
sm: "h-9 px-4 text-sm",
default: "h-10 px-6 text-sm",
lg: "h-12 px-8 text-base",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
)
export interface RainbowButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof rainbowButtonVariants> {
asChild?: boolean
}
const RainbowButton = React.forwardRef<HTMLButtonElement, RainbowButtonProps>(
({ className, variant, size, asChild = false, children, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
ref={ref}
className={cn("group isolate", rainbowButtonVariants({ variant, size }), className)}
{...props}
>
<span
aria-hidden
className="absolute inset-0 -z-10 animate-[spin_6s_linear_infinite] bg-[conic-gradient(#f97316,#f43f5e,#a855f7,#06b6d4,#22c55e,#f59e0b,#f97316)] opacity-80"
/>
<span aria-hidden className="absolute inset-[1px] -z-0 rounded-full bg-background/80" />
<span className="relative z-10 flex items-center gap-2">{children}</span>
</Comp>
)
},
)
RainbowButton.displayName = "RainbowButton"
export { RainbowButton, rainbowButtonVariants }