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

158 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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="grid grid-cols-3 gap-2">
{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}
aria-label={`${item.label}${item.description ? `${item.description}` : ""}`}
title={item.description}
onClick={() => onToggleLayer(item.id, !item.visible)}
className={cn(
"relative flex min-h-[76px] w-full flex-col items-center justify-center gap-1.5 border px-1.5 py-2 text-center transition duration-150 active:translate-y-px active:scale-[0.98]",
MAP_COMPACT_RADIUS_CLASS_NAME,
item.visible
? "border-blue-200 bg-blue-50/90 text-blue-950 shadow-sm shadow-blue-950/5"
: "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 transition-colors",
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="block w-full truncate text-xs font-semibold leading-tight">{item.label}</span>
</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>
);
}