mirror of
https://github.com/homarr-labs/dashboard-icons.git
synced 2026-01-12 16:25:38 +08:00
feat: add browser search rainbow button and dialog
Co-authored-by: ajnart <49837342+ajnart@users.noreply.github.com>
This commit is contained in:
152
web/src/components/add-to-search-bar-button.tsx
Normal file
152
web/src/components/add-to-search-bar-button.tsx
Normal 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'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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
58
web/src/registry/magicui/rainbow-button.tsx
Normal file
58
web/src/registry/magicui/rainbow-button.tsx
Normal 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 }
|
||||
Reference in New Issue
Block a user