feat: initialize drainage network frontend
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user