feat: initialize drainage network frontend

This commit is contained in:
2026-07-10 16:16:17 +08:00
commit 66de96d9e4
133 changed files with 33057 additions and 0 deletions
+126
View File
@@ -0,0 +1,126 @@
"use client";
import type { LucideIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger
} from "@/shared/ui/tooltip";
import {
MAP_CONTROL_HOVER_CLASS_NAME,
MAP_CONTROL_RADIUS_CLASS_NAME,
MAP_ICON_CELL_RADIUS_CLASS_NAME,
MAP_CONTROL_SURFACE_CLASS_NAME
} from "./map-control-styles";
export type MapToolbarItem = {
id: string;
label: string;
description?: string;
icon: LucideIcon;
active?: boolean;
disabled?: boolean;
badge?: string | number;
onClick?: () => void;
};
type MapToolbarProps = {
items: MapToolbarItem[];
label?: string;
orientation?: "vertical" | "horizontal";
className?: string;
};
export function MapToolbar({
items,
label = "地图工具",
orientation = "vertical",
className = ""
}: MapToolbarProps) {
const isVertical = orientation === "vertical";
return (
<TooltipProvider delayDuration={180}>
<nav
aria-label={label}
className={cn(
"pointer-events-auto p-[3px]",
MAP_CONTROL_RADIUS_CLASS_NAME,
MAP_CONTROL_SURFACE_CLASS_NAME,
isVertical ? "flex w-[46px] flex-col" : "inline-flex",
className
)}
>
{items.map((tool, index) => {
const Icon = tool.icon;
const title = tool.description ? `${tool.label}${tool.description}` : tool.label;
return (
<div key={tool.id} className={cn(isVertical ? "contents" : "flex items-center")}>
{index > 0 ? <ToolbarDivider vertical={isVertical} /> : null}
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
aria-label={title}
aria-pressed={tool.active}
disabled={tool.disabled}
onClick={tool.onClick}
className={cn(
"relative grid h-[38px] w-[38px] shrink-0 place-items-center text-slate-700 transition duration-150",
MAP_ICON_CELL_RADIUS_CLASS_NAME,
"focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-slate-900 focus-visible:outline-offset-2",
tool.active
? "bg-blue-50 text-blue-800 ring-1 ring-blue-300/70 hover:bg-blue-100 hover:text-blue-900"
: MAP_CONTROL_HOVER_CLASS_NAME,
tool.disabled && "cursor-not-allowed text-slate-400 opacity-60 hover:bg-transparent hover:text-slate-400 hover:ring-0 active:bg-transparent"
)}
>
<Icon size={20} strokeWidth={2.15} aria-hidden="true" />
<span className="sr-only">{tool.label}</span>
{tool.badge ? (
<span className="absolute -right-1 -top-1 grid min-w-4 place-items-center rounded-full bg-red-500 px-1 text-xs font-semibold leading-4 text-white ring-2 ring-white">
{tool.badge}
</span>
) : null}
</button>
</TooltipTrigger>
<TooltipContent
side={isVertical ? "left" : "top"}
sideOffset={8}
className="border border-slate-200 bg-white px-2.5 py-1.5 text-xs font-medium text-slate-800 shadow-lg"
>
{title}
</TooltipContent>
</Tooltip>
</div>
);
})}
</nav>
</TooltipProvider>
);
}
function ToolbarDivider({ vertical }: { vertical: boolean }) {
if (vertical) {
return (
<div
className="my-0.5 flex h-px w-full items-center justify-center"
aria-hidden="true"
>
<span className="block h-px w-3.5 bg-slate-300/70" />
</div>
);
}
return (
<div
className="mx-0.5 flex h-full w-px items-center justify-center"
aria-hidden="true"
>
<span className="block h-3.5 w-px bg-slate-300/70" />
</div>
);
}