// Rôle : rend l'en-tête, les contrôles et le contenu repliable d'un outil toolbox. import { Suspense, useEffect, useId, useRef, useState } from "react"; import { Icon } from "../../../components/Icon.jsx"; import { useInlineEdit } from "../../../hooks/useInlineEdit.js"; import { GuideChecklistControls } from "../../games/GuideChecklistControls.jsx"; import { useGuideChecklistPanel } from "../../games/useGuideChecklist.js"; import { ModuleDataGate, ModuleLoadingFallback } from "./ModuleDataGate.jsx"; import { supportsTextExchange } from "./textImport.js"; import { MODULE_COMPONENTS } from "./toolboxModuleComponents.jsx"; export function ModuleShell({ toolbox, module, context, reorder, itemGroup = "", spaces = [], activeSpaceId = "", moveText = "Déplacer vers", configurationText = {}, scrollIntoView = false, onScrolledIntoView, onRename, onUpdateModule, onDelete, onMoveToSpace }) { const definition = MODULE_COMPONENTS[module.type] || MODULE_COMPONENTS.notepad; const Component = definition.Component; const label = definition.label || module.type; const [editing, setEditing] = useState(false); const [configurationOpen, setConfigurationOpen] = useState(false); const [configurationTarget, setConfigurationTarget] = useState(null); const configurationId = useId(); const moduleRef = useRef(null); const guidePanel = useGuideChecklistPanel(context.guideChecklist, module); const hasAddPanel = definition.editable && definition.addable !== false; const hasTextExchange = supportsTextExchange(module.type); const hasConfiguration = spaces.length > 1 || Boolean(guidePanel.list) || hasTextExchange; const scrollable = definition.scrollable && module.scrollable === true; const collapsed = module.collapsed === true; const className = [ "module", reorder.isItemDragging(module.id) ? "is-dragging" : "", reorder.isItemDropTarget(module.id) ? "is-drop-target" : "", reorder.getDropPlacement("item", module.id) === "after" ? "drop-after" : "", scrollable ? "is-scrollable" : "", collapsed ? "is-collapsed" : "" ].filter(Boolean).join(" "); const componentProps = { toolboxId: toolbox.id, moduleId: module.id, context, editing, setEditing, openEditor: () => { setConfigurationOpen(false); setEditing(true); }, configurationTarget }; useEffect(() => { if (!scrollIntoView) return undefined; const frame = window.requestAnimationFrame(() => { moduleRef.current?.scrollIntoView({ behavior: "smooth", block: "center", inline: "nearest" }); onScrolledIntoView?.(); }); return () => window.cancelAnimationFrame(frame); }, [onScrolledIntoView, scrollIntoView]); useEffect(() => { if (!configurationOpen) return undefined; const closeOnEscape = (event) => { if (event.key === "Escape" && !document.querySelector('[aria-modal="true"]')) setConfigurationOpen(false); }; window.addEventListener("keydown", closeOnEscape); return () => window.removeEventListener("keydown", closeOnEscape); }, [configurationOpen]); function toggleCollapsed() { if (!collapsed) { setEditing(false); setConfigurationOpen(false); } onUpdateModule(module.id, (currentModule) => { const nextModule = { ...currentModule }; if (collapsed) { delete nextModule.collapsed; } else { nextModule.collapsed = true; } return nextModule; }); } return (
{definition.scrollable && (