Unify toolbox drag and drop reorder behavior
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s

This commit is contained in:
Shinuwa 2026-08-01 20:00:47 +02:00
parent a6d35c6e6b
commit 1879b245fb
17 changed files with 1258 additions and 929 deletions

View file

@ -2,6 +2,7 @@
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { Icon } from "../../../components/Icon.jsx";
import { Tabs } from "../../../components/Tabs.jsx";
import { moveItem, useGroupedReorder } from "../../../hooks/useGroupedReorder.js";
import {
formatDuration,
formatDurationWithCentiseconds,
@ -93,6 +94,18 @@ export function TimerModule({ toolboxId, moduleId, context }) {
if (!data.sortResults) return countdowns;
return [...countdowns].sort((a, b) => (a.targetMs || Number.POSITIVE_INFINITY) - (b.targetMs || Number.POSITIVE_INFINITY));
}, [data.countdowns, data.sortResults, nowMs]);
const countdownReorder = useGroupedReorder({
namespace: "timer-countdowns",
items: data.countdowns,
getItemId: (countdown) => countdown.id,
getParentId: () => moduleId,
canMoveItem: () => !data.sortResults,
onItemMove: (operation) => {
if (data.sortResults) return;
save({ ...data, countdowns: moveItem(data.countdowns, operation.sourceId, operation.targetId, operation.placement) });
},
hierarchy: { enabled: true, stickyParents: true }
});
function save(nextData) {
context.setModuleData(toolboxId, moduleId, nextData);
@ -300,7 +313,7 @@ export function TimerModule({ toolboxId, moduleId, context }) {
{activeTab === "stopwatch" ? (
<StopwatchLapList laps={data.stopwatch.laps} textContent={textContent} onRename={renameLap} onDelete={deleteLap} />
) : (
<CountdownList countdowns={visibleCountdowns} nowMs={nowMs} textContent={textContent} onRename={renameCountdown} onAlertModeChange={setCountdownAlertMode} onReset={resetCountdown} onDelete={deleteCountdown} />
<CountdownList countdowns={visibleCountdowns} nowMs={nowMs} textContent={textContent} reorder={countdownReorder} reorderEnabled={!data.sortResults} parentId={moduleId} onRename={renameCountdown} onAlertModeChange={setCountdownAlertMode} onReset={resetCountdown} onDelete={deleteCountdown} />
)}
</div>
</section>
@ -471,7 +484,7 @@ function StopwatchLapList({ laps, textContent, onRename, onDelete }) {
);
}
function CountdownList({ countdowns, nowMs, textContent, onRename, onAlertModeChange, onReset, onDelete }) {
function CountdownList({ countdowns, nowMs, textContent, reorder, reorderEnabled, parentId, onRename, onAlertModeChange, onReset, onDelete }) {
const [editingId, setEditingId] = useState("");
if (!countdowns.length) return <p className="muted">{textContent.emptyCountdowns || "Aucun compte à rebours configuré."}</p>;
return (
@ -482,9 +495,30 @@ function CountdownList({ countdowns, nowMs, textContent, onRename, onAlertModeCh
const canReset = countdown.type === "duration"
|| countdown.type === "interval"
|| (countdown.type === "daily_time" && expired);
const className = [
"timer-list-item",
expired ? "is-expired" : "",
editingId === countdown.id ? "is-editing" : "",
reorderEnabled && reorder.isItemDragging(countdown.id) ? "is-dragging" : "",
reorderEnabled && reorder.isItemDropTarget(countdown.id) ? "is-drop-target" : "",
reorderEnabled && reorder.getDropPlacement("item", countdown.id) === "after" ? "drop-after" : ""
].filter(Boolean).join(" ");
const reorderProps = reorderEnabled ? reorder.getItemProps({ itemId: countdown.id, parentId }) : {};
return (
<li key={countdown.id} className={`timer-list-item ${expired ? "is-expired" : ""} ${editingId === countdown.id ? "is-editing" : ""}`}>
<div className={`tool-split-entry timer-entry has-single-action ${editingId !== countdown.id ? "has-inline-controls" : ""} ${canReset && editingId !== countdown.id ? "has-inline-reset" : ""}`}>
<li key={countdown.id} className={className} {...reorderProps}>
<div className={`tool-split-entry timer-entry has-single-action ${reorderEnabled ? "has-drag-handle" : ""} ${editingId !== countdown.id ? "has-inline-controls" : ""} ${canReset && editingId !== countdown.id ? "has-inline-reset" : ""}`}>
{reorderEnabled && (
<button
className="timer-drag-handle"
type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, countdown.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${countdown.label}`}
title={textContent.reorderTitle || "Déplacer"}
>
<Icon name="drag" />
</button>
)}
{editingId === countdown.id ? (
<EditableTimerLabel
value={countdown.label}