127 lines
4.0 KiB
TypeScript
127 lines
4.0 KiB
TypeScript
"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>
|
||
);
|
||
}
|