Files
next-tjwater-drainage-frontend/features/map/core/components/layer-panel.tsx
T

163 lines
5.5 KiB
TypeScript

import { Check, Eye, EyeOff, Lock, Map } from "lucide-react";
import { cn } from "@/lib/utils";
import type { BaseLayerOption } from "./base-layers-control";
import type { MapLayerControlItem } from "./layer-control";
import {
MAP_COMPACT_RADIUS_CLASS_NAME,
MAP_ICON_CELL_RADIUS_CLASS_NAME,
MAP_READABLE_RADIUS_CLASS_NAME,
MAP_READABLE_SURFACE_CLASS_NAME,
MAP_READABLE_SURFACE_STRONG_CLASS_NAME
} from "./map-control-styles";
export type MapLayerPanelProps = {
layerItems: MapLayerControlItem[];
baseLayerOptions: BaseLayerOption[];
activeBaseLayerId: string;
onSelectBaseLayer: (id: string) => void;
onToggleLayer: (id: string, visible: boolean) => void;
};
export function MapLayerPanel({
layerItems,
baseLayerOptions,
activeBaseLayerId,
onSelectBaseLayer,
onToggleLayer
}: MapLayerPanelProps) {
return (
<>
<LayerVisibilitySection items={layerItems} onToggleLayer={onToggleLayer} />
<BaseLayerSection
options={baseLayerOptions}
activeLayerId={activeBaseLayerId}
onSelectLayer={onSelectBaseLayer}
/>
</>
);
}
type LayerVisibilitySectionProps = {
items: MapLayerControlItem[];
onToggleLayer: (id: string, visible: boolean) => void;
};
function LayerVisibilitySection({ items, onToggleLayer }: LayerVisibilitySectionProps) {
return (
<div>
<div className="mb-1.5 px-1 text-xs font-semibold text-slate-500">业务图层</div>
<div className="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
? "border-blue-100 bg-white/95 text-slate-900"
: "border-white/70 bg-white/95 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>
</div>
);
}
type BaseLayerSectionProps = {
options: BaseLayerOption[];
activeLayerId: string;
onSelectLayer: (id: string) => void;
};
function BaseLayerSection({ options, activeLayerId, onSelectLayer }: BaseLayerSectionProps) {
return (
<div>
<div className="mb-1.5 flex items-center justify-between px-1">
<span className="text-xs font-semibold text-slate-500">底图</span>
<Map size={14} aria-hidden="true" className="text-slate-400" />
</div>
<div className="grid grid-cols-3 gap-2">
{options.map((option) => {
const active = option.id === activeLayerId;
return (
<button
key={option.id}
type="button"
disabled={option.disabled}
aria-pressed={active}
onClick={() => onSelectLayer(option.id)}
className={cn(
"flex min-h-[76px] flex-col p-1 text-left transition duration-150",
MAP_READABLE_RADIUS_CLASS_NAME,
active ? MAP_READABLE_SURFACE_STRONG_CLASS_NAME : MAP_READABLE_SURFACE_CLASS_NAME,
active
? "border-blue-500 text-blue-700 ring-2 ring-blue-100"
: "text-slate-700 hover:border-blue-300 hover:bg-blue-50/40",
option.disabled && "cursor-not-allowed opacity-50"
)}
>
<BaseLayerSwatch option={option} active={active} />
<span className="mt-1 block w-full truncate px-0.5 text-xs font-semibold leading-none">
{option.label}
</span>
</button>
);
})}
</div>
</div>
);
}
type BaseLayerSwatchProps = {
option: BaseLayerOption;
active: boolean;
};
function BaseLayerSwatch({ option, active }: BaseLayerSwatchProps) {
return (
<span
className={cn(
"relative block min-h-0 flex-1 overflow-hidden border border-white shadow-inner",
MAP_ICON_CELL_RADIUS_CLASS_NAME,
option.swatchClassName ?? "bg-slate-100"
)}
>
{active ? (
<span className="absolute right-0.5 top-0.5 grid h-4 w-4 place-items-center rounded-full bg-blue-600 text-white">
<Check size={10} aria-hidden="true" />
</span>
) : null}
</span>
);
}