"use client"; import { useId } from "react"; import { ArrowDown, ArrowUp, GripVertical } from "lucide-react"; import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors, type DragEndEvent, } from "@dnd-kit/core"; import { SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { Button } from "@/components/ui/button"; import { reorderById } from "@/lib/insights-layout-reorder"; import { prefersReducedMotion } from "@/lib/charts/reduced-motion"; import { useTranslations } from "@/lib/i18n/context"; /** * v1.18.6 (W8 / MOD-03) — generic, controlled reorder list shared by the * Vorsorge / Illness / Labs settings pages. * * A single section (no active/inactive split — the secondary modules have a * flat list) with the same vocabulary as the medication order editor: an * `@dnd-kit` drag handle (pointer + keyboard sensors) plus per-row up/down * arrow buttons as the primary keyboard surface. Unlike the medication * editor it is fully controlled — there is no Save/Cancel draft state * because the host persists each move immediately to the localStorage order * (the choice is presentational, not a server write). The swap-and-renumber * logic is the shared `reorderById` helper so the order semantics match the * dashboard / insights / medications surfaces exactly. */ export interface ReorderItem { id: string; /** Primary label (bold). */ name: string; /** Optional secondary line (muted). */ secondary?: string; } interface ModuleOrderEditorProps { /** The list in its current effective order. */ items: ReorderItem[]; /** Persist the full id order after every move. */ onChange: (order: string[]) => void; } export function ModuleOrderEditor({ items, onChange }: ModuleOrderEditorProps) { const { t } = useTranslations(); const dragHintId = useId(); const ids = items.map((i) => i.id); const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 6 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }), ); const labels = { moveUp: t("dashboard.moveUp"), moveDown: t("dashboard.moveDown"), dragHandle: t("dashboard.dragHandle"), }; function applyReorder(fromId: string, toId: string) { onChange( reorderById( ids.map((id, order) => ({ id, order })), fromId, toId, ).map((r) => r.id), ); } function move(id: string, delta: -1 | 1) { const idx = ids.indexOf(id); const targetIdx = idx + delta; if (idx < 0 || targetIdx < 0 || targetIdx >= ids.length) return; applyReorder(id, ids[targetIdx]); } function handleDragEnd(event: DragEndEvent) { const { active, over } = event; if (!over || active.id === over.id) return; applyReorder(String(active.id), String(over.id)); } if (items.length === 0) { return (
{t("moduleList.reorder.empty")}
); } return ( // `relative` anchors the sr-only drag-hint paragraph: `sr-only` is // absolutely positioned, and without a positioned ancestor its 1px box // lands below the list in DOCUMENT coordinates and makes the document // itself scrollable (second scrollbar — UI-STANDARDS §9 one-scroll-floor).{t("dashboard.dragHandleHint")}