125 lines
3.9 KiB
TypeScript
125 lines
3.9 KiB
TypeScript
import type { LucideIcon } from "lucide-react";
|
||
import { cn } from "@/shared/ui/cn";
|
||
import {
|
||
Tooltip,
|
||
TooltipContent,
|
||
TooltipProvider,
|
||
TooltipTrigger
|
||
} from "@/shared/ui/tooltip";
|
||
import {
|
||
MAP_CONTROL_HOVER_CLASS_NAME,
|
||
mapOptionInteractionVariants,
|
||
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-1",
|
||
MAP_CONTROL_RADIUS_CLASS_NAME,
|
||
MAP_CONTROL_SURFACE_CLASS_NAME,
|
||
isVertical ? "flex w-fit 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-10 w-10 shrink-0 place-items-center border border-transparent text-slate-700",
|
||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||
tool.active
|
||
? cn("border-[var(--action-blue)] bg-[var(--action-blue)] text-white shadow-[var(--action-shadow-primary)]", mapOptionInteractionVariants({ state: "selected" }))
|
||
: MAP_CONTROL_HOVER_CLASS_NAME,
|
||
tool.disabled && "text-[var(--ink-muted)] opacity-45"
|
||
)}
|
||
>
|
||
<Icon size={20} strokeWidth={2.15} aria-hidden="true" />
|
||
<span className="sr-only">{tool.label}</span>
|
||
{tool.badge ? (
|
||
<span className="status-tone-danger absolute -right-1 -top-1 grid min-w-4 place-items-center rounded-full bg-[var(--status-mark)] 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-[var(--surface-divider)] bg-[var(--surface-reading)] 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>
|
||
);
|
||
}
|