2026-07-28 10:31:07 +07:00
|
|
|
"use client";
|
|
|
|
|
|
2026-08-14 10:49:44 +07:00
|
|
|
import { Search } from "lucide-react";
|
|
|
|
|
import { useState } from "react";
|
|
|
|
|
import { Dialog } from "@/components/primitives/dialog";
|
|
|
|
|
import { Input } from "@/components/primitives/input";
|
|
|
|
|
import { cn } from "@/lib/utils";
|
2026-07-28 10:31:07 +07:00
|
|
|
|
2026-08-14 10:49:44 +07:00
|
|
|
export interface Message {
|
|
|
|
|
id: string;
|
|
|
|
|
content: string;
|
|
|
|
|
username: string;
|
|
|
|
|
channel: string;
|
|
|
|
|
time: string;
|
2026-07-28 10:31:07 +07:00
|
|
|
}
|
|
|
|
|
|
2026-08-14 10:49:44 +07:00
|
|
|
export interface SearchOverlayProps {
|
|
|
|
|
open: boolean;
|
|
|
|
|
onClose: () => void;
|
|
|
|
|
results: Message[];
|
|
|
|
|
onSelect: (msg: Message) => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function SearchOverlay({
|
|
|
|
|
open,
|
|
|
|
|
onClose,
|
|
|
|
|
results,
|
|
|
|
|
onSelect,
|
|
|
|
|
}: SearchOverlayProps) {
|
2026-07-28 10:31:07 +07:00
|
|
|
const [query, setQuery] = useState("");
|
|
|
|
|
|
2026-08-14 10:49:44 +07:00
|
|
|
const filtered = query
|
|
|
|
|
? results.filter(
|
|
|
|
|
(m) =>
|
|
|
|
|
m.content.toLowerCase().includes(query.toLowerCase()) ||
|
|
|
|
|
m.username.toLowerCase().includes(query.toLowerCase()),
|
|
|
|
|
)
|
|
|
|
|
: results;
|
2026-07-28 10:31:07 +07:00
|
|
|
|
|
|
|
|
return (
|
2026-08-14 10:49:44 +07:00
|
|
|
<Dialog open={open} onClose={onClose} className="p-0 max-w-xl">
|
|
|
|
|
<div className="p-4">
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-[var(--color-ink-soft)]" />
|
|
|
|
|
<Input
|
|
|
|
|
autoFocus
|
|
|
|
|
placeholder="Search messages… (Esc to close)"
|
2026-07-28 10:31:07 +07:00
|
|
|
value={query}
|
|
|
|
|
onChange={(e) => setQuery(e.target.value)}
|
2026-08-14 10:49:44 +07:00
|
|
|
className="pl-9 font-mono"
|
2026-07-28 10:31:07 +07:00
|
|
|
/>
|
|
|
|
|
</div>
|
2026-08-14 10:49:44 +07:00
|
|
|
<div className="mt-3 max-h-[420px] overflow-y-auto">
|
|
|
|
|
{filtered.length === 0 ? (
|
|
|
|
|
<p className="py-6 text-center text-sm text-[var(--color-ink-soft)]">
|
|
|
|
|
No results.
|
|
|
|
|
</p>
|
2026-07-28 10:31:07 +07:00
|
|
|
) : (
|
2026-08-14 10:49:44 +07:00
|
|
|
<div className="flex flex-col gap-1">
|
|
|
|
|
{filtered.slice(0, 32).map((m) => (
|
|
|
|
|
<button
|
|
|
|
|
key={m.id}
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => onSelect(m)}
|
|
|
|
|
className="group flex flex-col items-start gap-1 rounded-[var(--radius-r-control)] px-2.5 py-2 text-left transition-colors hover:bg-[var(--color-surface-2)]"
|
|
|
|
|
>
|
|
|
|
|
<span className="text-xs text-[var(--color-ink-soft)] group-hover:text-[var(--color-ink)]">
|
|
|
|
|
#{m.channel} · {m.username}
|
2026-08-01 09:19:39 +07:00
|
|
|
</span>
|
2026-08-14 10:49:44 +07:00
|
|
|
<span className="text-sm">{m.content}</span>
|
|
|
|
|
<span className="text-[10px] text-[var(--color-ink-soft)]/60">
|
|
|
|
|
{m.time}
|
2026-08-01 09:19:39 +07:00
|
|
|
</span>
|
2026-08-14 10:49:44 +07:00
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-07-28 10:31:07 +07:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-08-14 10:49:44 +07:00
|
|
|
</Dialog>
|
2026-07-28 10:31:07 +07:00
|
|
|
);
|
|
|
|
|
}
|