import { useState } from "react";
import { Bookmark, Plus, X } from "lucide-react";
import clsx from "clsx";
import Box from "../Box.tsx";
import CardHeader from "../CardHeader.tsx";
import PrimaryButton from "../buttons/PrimaryButton.tsx";
import type { Marker } from "../../utils/types.ts";
import { formatLocalTime } from "../../utils/utils.ts";
const inputClass = "border border-hairline bg-fields rounded-md w-full p-2 text-sm focus:outline-none focus:ring-2 focus:ring-muted transition-colors";
type RailSegment = "none" | "plain" | "lit";
const railClass = (segment: RailSegment) => clsx(
"w-px",
segment === "lit" && "bg-terracotta",
segment === "plain" && "bg-inactive-strong/60",
segment === "none" && "bg-transparent"
);
type MarkerRowProps = {
marker: Marker;
role: "start" | "end" | "between" | null;
/** Rail segments above and below the dot — "lit" inside the chosen section. */
railAbove: RailSegment;
railBelow: RailSegment;
onSelect: () => void;
onDelete: () => void;
};
const MarkerRow = ({ marker, role, railAbove, railBelow, onSelect, onDelete }: MarkerRowProps) => (
{/* Timeline rail: a dot per marker, with the segment between the chosen
start and end lit so the selected section reads as one run. */}
{/* The rail spans the full row, so the breathing room goes on the button —
the line stays continuous while neighbouring rings no longer touch. */}
);
type Props = {
/** Newest first — the list renders them in the order given. */
markers: Marker[];
startMarkerId: string;
endMarkerId: string;
error: string | null;
/** Resolves true once the marker exists, so the input only clears on success. */
onAdd: (message: string) => Promise;
onSelect: (id: number) => void;
onDelete: (id: number) => void;
className?: string;
};
const MarkerList = ({ markers, startMarkerId, endMarkerId, error, onAdd, onSelect, onDelete, className }: Props) => {
const [message, setMessage] = useState("");
const [busy, setBusy] = useState(false);
const handleAdd = async () => {
if (!message.trim() || busy) return;
setBusy(true);
if (await onAdd(message.trim())) setMessage("");
setBusy(false);
};
// Either of the two may have been picked first, so the lit run is bounded by
// whichever indices come first/last rather than by start/end.
const startIndex = markers.findIndex((m) => String(m.id) === startMarkerId);
const endIndex = markers.findIndex((m) => String(m.id) === endMarkerId);
const hasRange = startIndex !== -1 && endIndex !== -1;
const rangeFrom = hasRange ? Math.min(startIndex, endIndex) : -1;
const rangeTo = hasRange ? Math.max(startIndex, endIndex) : -1;
// The rail segment sitting between marker `i` and the one after it.
const railSegment = (i: number): RailSegment => {
if (i < 0 || i >= markers.length - 1) return "none";
return hasRange && i >= rangeFrom && i < rangeTo ? "lit" : "plain";
};
return (
{markers.length > 0 && (
{markers.length}
)}
{markers.length === 0 ? (
Nothing marked yet. Markers stack up here, newest first — pick two to save the section between them.
) : (
/* py-0.5 keeps the scroll container from clipping the selection
ring on the first and last rows. */
{markers.map((m, i) => (
rangeFrom && i < rangeTo ? "between"
: null}
railAbove={railSegment(i - 1)}
railBelow={railSegment(i)}
onSelect={() => onSelect(m.id)}
onDelete={() => onDelete(m.id)}
/>
))}
)}
);
};
export default MarkerList;