2026-08-17 13:49:55 +02:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useState, type FormEvent } from "react";
|
|
|
|
|
import type { SearchEngine, Widget } from "@/lib/config/schema";
|
2026-08-17 17:21:18 +02:00
|
|
|
import { WidgetCard } from "@/components/widgets/WidgetCard";
|
2026-08-17 13:49:55 +02:00
|
|
|
|
|
|
|
|
type SearchWidget = Extract<Widget, { type: "search" }>;
|
|
|
|
|
|
|
|
|
|
const ENGINE_URLS: Record<string, string> = {
|
|
|
|
|
duckduckgo: "https://duckduckgo.com/?q=",
|
|
|
|
|
google: "https://www.google.com/search?q=",
|
|
|
|
|
bing: "https://www.bing.com/search?q=",
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-17 17:21:18 +02:00
|
|
|
const ENGINE_LABEL: Record<string, string> = {
|
|
|
|
|
duckduckgo: "DuckDuckGo",
|
|
|
|
|
google: "Google",
|
|
|
|
|
bing: "Bing",
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-17 13:49:55 +02:00
|
|
|
export function SearchWidget({ widget }: { widget: SearchWidget }) {
|
|
|
|
|
const [engine, setEngine] = useState(widget.defaultEngine ?? widget.engines[0]);
|
|
|
|
|
const [query, setQuery] = useState("");
|
|
|
|
|
|
|
|
|
|
function handleSubmit(event: FormEvent) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
const trimmed = query.trim();
|
|
|
|
|
if (!trimmed) return;
|
|
|
|
|
window.open(`${ENGINE_URLS[engine]}${encodeURIComponent(trimmed)}`, "_blank", "noreferrer");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
2026-08-17 17:21:18 +02:00
|
|
|
<WidgetCard span={4}>
|
|
|
|
|
<form onSubmit={handleSubmit} className="flex flex-1 flex-col justify-center gap-2">
|
|
|
|
|
<div className="text-[10px] tracking-[0.1em] text-accent uppercase">Search</div>
|
|
|
|
|
<input
|
|
|
|
|
value={query}
|
|
|
|
|
onChange={(event) => setQuery(event.target.value)}
|
|
|
|
|
placeholder="Search the web…"
|
|
|
|
|
autoComplete="off"
|
|
|
|
|
className="min-h-9 w-full rounded-md border border-border bg-bg px-2.5 py-1.5 text-sm text-fg outline-none placeholder:text-fg-muted focus-visible:border-accent"
|
|
|
|
|
/>
|
|
|
|
|
{widget.engines.length > 1 && (
|
|
|
|
|
<div className="inline-flex self-start overflow-hidden rounded-md border border-border">
|
|
|
|
|
{widget.engines.map((option) => (
|
|
|
|
|
<label
|
|
|
|
|
key={option}
|
|
|
|
|
className={`cursor-pointer px-2.5 py-1 text-[12px] first:border-l-0 [&+label]:border-l [&+label]:border-border ${
|
|
|
|
|
option === engine ? "text-accent shadow-[inset_0_0_0_1px_var(--pn-accent)]" : "text-fg-muted"
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="radio"
|
|
|
|
|
name="engine"
|
|
|
|
|
value={option}
|
|
|
|
|
checked={option === engine}
|
|
|
|
|
onChange={() => setEngine(option as SearchEngine)}
|
|
|
|
|
className="sr-only"
|
|
|
|
|
/>
|
|
|
|
|
{ENGINE_LABEL[option] ?? option}
|
|
|
|
|
</label>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</form>
|
|
|
|
|
</WidgetCard>
|
2026-08-17 13:49:55 +02:00
|
|
|
);
|
|
|
|
|
}
|