sokko-g/website/src/features/toolboxes/modules/CountersModule.jsx
Shinuwa 9b72f4934c
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
Add toolbox library page with editable examples
2026-08-03 11:37:59 +02:00

92 lines
4.1 KiB
JavaScript

// Rôle : fournit l'outil compteurs personnalisables.
import { Icon } from "../../../components/Icon.jsx";
import { useDraftForm } from "../../../hooks/useDraftForm.js";
import { moveItem, useGroupedReorder } from "../../../hooks/useGroupedReorder.js";
export function CountersModule({ toolboxId, moduleId, context, editing }) {
const data = context.normalizeCountersData(context.getModuleData(toolboxId, moduleId, { counters: [] }));
const textContent = context.moduleText?.counters || {};
const counterDraft = useDraftForm({ label: "" });
const {
itemReorder,
getItemProps,
isItemDragging,
isItemDropTarget,
getDropPlacement
} = useGroupedReorder({
namespace: "counters",
items: data.counters,
getItemId: (counter) => counter.id,
getParentId: () => moduleId,
orientation: "horizontal",
onItemMove: (operation) => save(moveItem(data.counters, operation.sourceId, operation.targetId, operation.placement)),
hierarchy: { enabled: true, stickyParents: true }
});
function save(counters) {
context.setModuleData(toolboxId, moduleId, { counters });
}
function addCounter(event) {
counterDraft.handleSubmit(event, (draft, { reset }) => {
const cleanLabel = draft.label.trim();
if (!cleanLabel) return;
save([...data.counters, { id: context.uid("counter"), label: cleanLabel, value: 0 }]);
reset();
});
}
function updateCounter(counterId, updater) {
save(data.counters.map((counter) => counter.id === counterId ? updater(counter) : counter));
}
return (
<>
{editing && (
<form className="inline-form counters-add-form module-add-panel" onSubmit={addCounter}>
<input {...counterDraft.getFieldProps("label", { placeholder: textContent.labelPlaceholder || "Nom du compteur" })} />
<button className="primary">{textContent.addButton || "Ajouter"}</button>
</form>
)}
<div className="counters-grid">
{data.counters.map((counter) => {
const className = [
"counter-item",
isItemDragging(counter.id) ? "is-dragging" : "",
isItemDropTarget(counter.id) ? "is-drop-target" : "",
getDropPlacement("item", counter.id) === "after" ? "drop-after" : ""
].filter(Boolean).join(" ");
return (
<article className={className} key={counter.id} {...getItemProps({ itemId: counter.id, parentId: moduleId })}>
<button
className="counter-drag-handle"
type="button"
onPointerDown={(event) => itemReorder.startDrag(event, counter.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${counter.label}`}
title={textContent.reorderTitle || "Déplacer"}
>
<Icon name="drag" />
</button>
<div className="counter-value">
<strong>{counter.value}</strong>
<span>{counter.label}</span>
</div>
<div className="counter-actions">
<button type="button" onClick={() => updateCounter(counter.id, (item) => ({ ...item, value: item.value - 1 }))} aria-label={`${textContent.decrementLabel || "Décrémenter"} ${counter.label}`}>-</button>
<button type="button" onClick={() => updateCounter(counter.id, (item) => ({ ...item, value: item.value + 1 }))} aria-label={`${textContent.incrementLabel || "Incrémenter"} ${counter.label}`}>+</button>
<button type="button" onClick={() => updateCounter(counter.id, (item) => ({ ...item, value: 0 }))} aria-label={`${textContent.resetTitle || "Réinitialiser"} ${counter.label}`} title={textContent.resetTitle || "Réinitialiser"}>
<Icon name="rubber" />
</button>
<button type="button" className="danger" onClick={() => save(data.counters.filter((item) => item.id !== counter.id))} aria-label={`${textContent.deleteTitle || "Supprimer"} ${counter.label}`} title={textContent.deleteTitle || "Supprimer"}>
<Icon name="trash" />
</button>
</div>
</article>
);
})}
</div>
</>
);
}