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

164 lines
4.8 KiB
TypeScript

import type { LucideIcon } from "lucide-react";
import { Copy, Ruler, Trash2 } from "lucide-react";
import { cn } from "@/lib/utils";
import {
MapActionRow,
MapModeButton,
MapPanelSection
} from "./control-panel";
import {
MAP_COMPACT_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 MapMeasureMode = {
id: string;
label: string;
icon: LucideIcon;
disabled?: boolean;
};
export type MapMeasureUnit = {
id: string;
label: string;
};
export type MapMeasureResultMetric = {
label: string;
value: string;
};
export type MapMeasureAction = {
id: string;
label: string;
description: string;
icon?: LucideIcon;
strong?: boolean;
variant?: "default" | "primary";
tone?: "default" | "danger";
disabled?: boolean;
onClick?: () => void;
};
export type MapMeasurePanelProps = {
modes: MapMeasureMode[];
activeModeId: string;
resultLabel: string;
resultValue: string;
resultUnit: string;
metrics?: MapMeasureResultMetric[];
units: MapMeasureUnit[];
activeUnitId: string;
actions: MapMeasureAction[];
onSelectMode?: (id: string) => void;
onSelectUnit?: (id: string) => void;
};
export function MapMeasurePanel({
modes,
activeModeId,
resultLabel,
resultValue,
resultUnit,
metrics = [],
units,
activeUnitId,
actions,
onSelectMode,
onSelectUnit
}: MapMeasurePanelProps) {
return (
<div className="space-y-3">
<MapPanelSection title="模式">
<div className={cn("grid grid-cols-3 gap-1.5 border border-white/60 bg-white/80 p-1.5", MAP_READABLE_RADIUS_CLASS_NAME)}>
{modes.map((mode) => (
<MapModeButton
key={mode.id}
icon={mode.icon}
label={mode.label}
active={mode.id === activeModeId}
disabled={mode.disabled}
onClick={() => onSelectMode?.(mode.id)}
/>
))}
</div>
</MapPanelSection>
<MapPanelSection title="结果">
<div className={cn("border border-blue-100 bg-blue-50/95 p-2", MAP_READABLE_RADIUS_CLASS_NAME)}>
<div className={cn("p-3", MAP_READABLE_RADIUS_CLASS_NAME, MAP_READABLE_SURFACE_STRONG_CLASS_NAME)}>
<div className="min-w-0 border-b border-slate-100 pb-3">
<div className="text-xs font-semibold text-blue-700">{resultLabel}</div>
<div className="mt-1 flex min-w-0 items-baseline gap-1.5 text-slate-950">
<span className="truncate text-2xl font-semibold leading-none">{resultValue}</span>
<span className="shrink-0 text-xs font-semibold text-slate-500">{resultUnit}</span>
</div>
</div>
{metrics.length > 0 ? (
<div className="mt-2 grid grid-cols-[repeat(auto-fit,minmax(92px,1fr))] gap-1.5">
{metrics.map((metric) => (
<div
key={metric.label}
className={cn("min-w-0 px-2.5 py-1.5 text-left", MAP_COMPACT_RADIUS_CLASS_NAME, MAP_READABLE_SURFACE_CLASS_NAME)}
>
<div className="truncate text-xs text-slate-500">{metric.label}</div>
<div className="mt-0.5 truncate text-sm font-semibold text-slate-900">{metric.value}</div>
</div>
))}
</div>
) : null}
</div>
<div className="mt-3 grid grid-cols-[repeat(auto-fit,minmax(0,1fr))] gap-1.5">
{units.map((unit) => (
<button
key={unit.id}
type="button"
onClick={() => onSelectUnit?.(unit.id)}
className={cn(
"h-7 text-xs font-semibold transition",
MAP_COMPACT_RADIUS_CLASS_NAME,
unit.id === activeUnitId
? "bg-blue-600 text-white"
: "bg-white/95 text-slate-600 hover:text-blue-700"
)}
>
{unit.label}
</button>
))}
</div>
</div>
</MapPanelSection>
<MapPanelSection title="操作">
{actions.map((action) => (
<MapActionRow
key={action.id}
icon={action.icon ?? getDefaultActionIcon(action.id)}
label={action.label}
description={action.description}
strong={action.strong}
variant={action.variant}
tone={action.tone}
disabled={action.disabled}
onClick={action.onClick}
/>
))}
</MapPanelSection>
</div>
);
}
function getDefaultActionIcon(actionId: string) {
if (actionId.includes("copy")) {
return Copy;
}
if (actionId.includes("clear") || actionId.includes("delete")) {
return Trash2;
}
return Ruler;
}