fix(ui): ограничить форму маршрута без потери конечных точек
This commit is contained in:
1 parent
9124d451a5
commit
06850169ba
8 files changed
+135
-51
No files matched your search
@@ -0,0 +1,50 @@
|
||||
"use client"
|
||||
import { useMemo, useState } from "react"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { CheckIcon } from "lucide-react"
|
||||
export interface GatewayOption { id: string; name: string; ip: string; status: "active" | "inactive" | "unknown" }
|
||||
export function RecursiveGatewayChoices({ gateways, selectedValue, endpointNumber, onChange }: { gateways: GatewayOption[]; selectedValue: string; endpointNumber: number; onChange: (value: string) => void }) {
|
||||
const [search, setSearch] = useState("")
|
||||
const matches = useMemo(() => gateways.filter(g => (g.ip + "%" + g.name).toLowerCase().includes(search.toLowerCase())), [gateways, search])
|
||||
const selected = matches.find(g => g.ip + "%" + g.name === selectedValue)
|
||||
const visible = (selected ? [selected, ...matches.filter(g => g.id !== selected.id)] : matches).slice(0, 5)
|
||||
return <div className="flex flex-col gap-1.5">
|
||||
{gateways.length > 5 && <Input aria-label={"Поиск шлюза для endpoint " + endpointNumber} placeholder="IP или имя шлюза…" value={search} onChange={event => setSearch(event.target.value)} />}
|
||||
{gateways.length > 5 && <p role="status" className="text-xs text-muted-foreground">Показано {visible.length} из {matches.length} шлюзов. Поиск охватывает весь список.</p>}
|
||||
{search && matches.length === 0 && <p role="status" className="text-xs text-muted-foreground">Шлюзы не найдены. Измените поиск или введите адрес вручную.</p>}
|
||||
{visible.map((g) => {
|
||||
const value = `${g.ip}%${g.name}`
|
||||
const selected = selectedValue === value
|
||||
return (
|
||||
<button
|
||||
key={g.id}
|
||||
type="button"
|
||||
aria-label={`Выбрать шлюз ${value}`}
|
||||
aria-pressed={selected}
|
||||
onClick={() => onChange(value)}
|
||||
className={cn(
|
||||
"text-left rounded-lg border px-3 py-2.5 transition-all",
|
||||
selected
|
||||
? "border-primary bg-primary/5 ring-1 ring-primary/30"
|
||||
: "border-border hover:border-muted-foreground/40 hover:bg-muted/40",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={cn("size-1.5 rounded-full shrink-0", g.status === "active" ? "bg-success" : "bg-muted-foreground")} />
|
||||
<span className="font-mono text-xs font-semibold flex-1 truncate">{g.name}</span>
|
||||
<span className="text-xs text-muted-foreground">{g.status === "active" ? "Активный маршрут" : g.status === "inactive" ? "Неактивный маршрут" : "Статус неизвестен"}</span>
|
||||
{selected && <CheckIcon aria-hidden="true" className="size-3.5 text-primary shrink-0" />}
|
||||
</div>
|
||||
<div className="mt-1.5 grid grid-cols-[auto_auto_1fr] items-start gap-2 text-[11px] text-muted-foreground font-mono">
|
||||
<span className="text-muted-foreground/50 break-all">{g.ip}</span>
|
||||
<span className="text-muted-foreground/30">→</span>
|
||||
<span className={cn("min-w-0 break-all whitespace-normal leading-tight", selected ? "text-primary font-medium" : "")}>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
Reference in new issue
Block a user