feat(frontend): système de plugins — runtime, menus, layouts custom (#43)
Lots F1-F4 : runtime de chargement/registre plugin, extension des menus existants, panneau de gestion des plugins, types de layout custom (sélecteur, fallback, cellule dédiée) branchés sur le port plugin. Suite npm typecheck/test verte (947/947). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
76
frontend/src/features/plugins/PluginConfirmDialog.tsx
Normal file
76
frontend/src/features/plugins/PluginConfirmDialog.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* A small modal confirmation, local to the plugins feature (ticket #43, F2).
|
||||
* Mirrors `features/devices/ConfirmDialog` (not shared — that dialog is itself
|
||||
* feature-local by design, see its header comment); duplicated rather than
|
||||
* cross-imported to keep each feature's public surface to its own `index.ts`.
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { Button, zIndex } from "@/shared";
|
||||
|
||||
interface PluginConfirmDialogProps {
|
||||
title: string;
|
||||
body: string;
|
||||
confirmLabel: string;
|
||||
danger?: boolean;
|
||||
busy?: boolean;
|
||||
onConfirm: () => void | Promise<void>;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export function PluginConfirmDialog({
|
||||
title,
|
||||
body,
|
||||
confirmLabel,
|
||||
danger = false,
|
||||
busy = false,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
}: PluginConfirmDialogProps) {
|
||||
const cancelRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
cancelRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
if (e.key === "Escape") onCancel();
|
||||
}
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
return () => window.removeEventListener("keydown", onKeyDown);
|
||||
}, [onCancel]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 flex items-center justify-center bg-black/50 p-4"
|
||||
style={{ zIndex: zIndex.floatingWindow }}
|
||||
onClick={onCancel}
|
||||
>
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={title}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="flex w-full max-w-md flex-col gap-3 rounded-lg border border-border bg-raised p-4 shadow-xl"
|
||||
>
|
||||
<h3 className="text-sm font-semibold text-content">{title}</h3>
|
||||
<p className="whitespace-pre-line text-sm text-muted">{body}</p>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button ref={cancelRef} size="sm" variant="ghost" onClick={onCancel}>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={danger ? "danger" : "primary"}
|
||||
loading={busy}
|
||||
onClick={() => void onConfirm()}
|
||||
>
|
||||
{confirmLabel}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user