104 lines
3.5 KiB
TypeScript
104 lines
3.5 KiB
TypeScript
import { Eye, EyeOff, Layers3, Lock } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
import {
|
|
MAP_COMPACT_RADIUS_CLASS_NAME,
|
|
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
|
MAP_MAJOR_PANEL_RADIUS_CLASS_NAME,
|
|
MAP_TOOL_PANEL_SURFACE_CLASS_NAME
|
|
} from "./map-control-styles";
|
|
|
|
export type MapLayerControlItem = {
|
|
id: string;
|
|
label: string;
|
|
description?: string;
|
|
visible: boolean;
|
|
disabled?: boolean;
|
|
locked?: boolean;
|
|
};
|
|
|
|
type MapLayerControlProps = {
|
|
title?: string;
|
|
items: MapLayerControlItem[];
|
|
onToggleLayer: (id: string, visible: boolean) => void;
|
|
className?: string;
|
|
};
|
|
|
|
export function MapLayerControl({
|
|
title = "图层",
|
|
items,
|
|
onToggleLayer,
|
|
className = ""
|
|
}: MapLayerControlProps) {
|
|
return (
|
|
<section
|
|
aria-label={title}
|
|
className={cn(
|
|
"pointer-events-auto w-[276px] p-3",
|
|
MAP_MAJOR_PANEL_RADIUS_CLASS_NAME,
|
|
MAP_TOOL_PANEL_SURFACE_CLASS_NAME,
|
|
className
|
|
)}
|
|
>
|
|
<div className="flex items-center justify-between gap-3 px-1">
|
|
<div className="flex min-w-0 items-center gap-2">
|
|
<span className={cn("grid h-8 w-8 shrink-0 place-items-center bg-blue-50 text-blue-700", MAP_ICON_CELL_RADIUS_CLASS_NAME)}>
|
|
<Layers3 size={17} aria-hidden="true" />
|
|
</span>
|
|
<div className="min-w-0">
|
|
<h2 className="truncate text-sm font-semibold leading-tight text-slate-950">{title}</h2>
|
|
<p className="mt-0.5 text-xs text-slate-500">
|
|
{items.filter((item) => item.visible).length}/{items.length} 可见
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-3 space-y-1.5">
|
|
{items.map((item) => {
|
|
const Icon = item.visible ? Eye : EyeOff;
|
|
const disabled = item.disabled || item.locked;
|
|
|
|
return (
|
|
<button
|
|
key={item.id}
|
|
type="button"
|
|
disabled={disabled}
|
|
aria-pressed={item.visible}
|
|
onClick={() => onToggleLayer(item.id, !item.visible)}
|
|
className={cn(
|
|
"flex min-h-12 w-full items-center gap-3 border px-2.5 text-left text-sm transition duration-150",
|
|
MAP_COMPACT_RADIUS_CLASS_NAME,
|
|
item.visible
|
|
? "surface-control border-blue-100 text-slate-900"
|
|
: "surface-well border-transparent text-slate-500 hover:border-slate-200 hover:bg-white",
|
|
disabled && "cursor-not-allowed opacity-60 hover:bg-transparent"
|
|
)}
|
|
>
|
|
<span
|
|
className={cn(
|
|
"grid h-8 w-8 shrink-0 place-items-center",
|
|
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
|
item.visible ? "bg-blue-600 text-white" : "bg-slate-100 text-slate-400"
|
|
)}
|
|
>
|
|
{item.locked ? <Lock size={15} aria-hidden="true" /> : <Icon size={15} aria-hidden="true" />}
|
|
</span>
|
|
<span className="min-w-0 flex-1">
|
|
<span className="block truncate font-semibold">{item.label}</span>
|
|
{item.description ? <span className="block truncate text-xs text-slate-500">{item.description}</span> : null}
|
|
</span>
|
|
<span
|
|
className={cn(
|
|
"h-2.5 w-2.5 rounded-full",
|
|
item.visible ? "bg-blue-600" : "bg-slate-300"
|
|
)}
|
|
aria-hidden="true"
|
|
/>
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|