Add jsinspect-plus (AST-based structural duplication detector) to pnpm check with threshold 50 / min 3 instances. Fix all findings: - Extract condition icon/color maps to shared condition-styles.ts - Extract useClickOutside hook (5 components) - Extract dispatchAction + resolveAndRename in use-encounter - Extract runEncounterAction in application layer (13 use cases) - Extract findCombatant helper in domain (9 functions) - Extract TraitSection in stat-block (4 trait rendering blocks) - Extract DialogHeader in dialog.tsx (4 dialogs) Net result: -263 lines across 40 files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
94 lines
2.7 KiB
TypeScript
94 lines
2.7 KiB
TypeScript
import { Check, ClipboardCopy, Download } from "lucide-react";
|
|
import { useCallback, useState } from "react";
|
|
import { Dialog, DialogHeader } from "./ui/dialog.js";
|
|
import { Input } from "./ui/input.js";
|
|
|
|
interface ExportMethodDialogProps {
|
|
open: boolean;
|
|
onDownload: (includeHistory: boolean, filename: string) => void;
|
|
onCopyToClipboard: (includeHistory: boolean) => void;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export function ExportMethodDialog({
|
|
open,
|
|
onDownload,
|
|
onCopyToClipboard,
|
|
onClose,
|
|
}: Readonly<ExportMethodDialogProps>) {
|
|
const [includeHistory, setIncludeHistory] = useState(false);
|
|
const [filename, setFilename] = useState("");
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
const handleClose = useCallback(() => {
|
|
setIncludeHistory(false);
|
|
setFilename("");
|
|
setCopied(false);
|
|
onClose();
|
|
}, [onClose]);
|
|
|
|
return (
|
|
<Dialog open={open} onClose={handleClose} className="w-80">
|
|
<DialogHeader title="Export Encounter" onClose={handleClose} />
|
|
<div className="mb-3">
|
|
<Input
|
|
type="text"
|
|
value={filename}
|
|
onChange={(e) => setFilename(e.target.value)}
|
|
placeholder="Filename (optional)"
|
|
/>
|
|
</div>
|
|
<label className="mb-4 flex items-center gap-2 text-sm">
|
|
<input
|
|
type="checkbox"
|
|
checked={includeHistory}
|
|
onChange={(e) => setIncludeHistory(e.target.checked)}
|
|
className="accent-accent"
|
|
/>
|
|
<span className="text-foreground">Include undo/redo history</span>
|
|
</label>
|
|
<div className="flex flex-col gap-2">
|
|
<button
|
|
type="button"
|
|
className="flex items-center gap-3 rounded-lg border border-border px-4 py-3 text-left text-foreground text-sm hover:bg-hover-neutral-bg"
|
|
onClick={() => {
|
|
onDownload(includeHistory, filename);
|
|
handleClose();
|
|
}}
|
|
>
|
|
<Download className="h-5 w-5 text-muted-foreground" />
|
|
<div>
|
|
<div className="font-medium">Download file</div>
|
|
<div className="text-muted-foreground text-xs">
|
|
Save as a JSON file
|
|
</div>
|
|
</div>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="flex items-center gap-3 rounded-lg border border-border px-4 py-3 text-left text-foreground text-sm hover:bg-hover-neutral-bg"
|
|
onClick={() => {
|
|
onCopyToClipboard(includeHistory);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
}}
|
|
>
|
|
{copied ? (
|
|
<Check className="h-5 w-5 text-green-400" />
|
|
) : (
|
|
<ClipboardCopy className="h-5 w-5 text-muted-foreground" />
|
|
)}
|
|
<div>
|
|
<div className="font-medium">
|
|
{copied ? "Copied!" : "Copy to clipboard"}
|
|
</div>
|
|
<div className="text-muted-foreground text-xs">
|
|
Copy JSON to your clipboard
|
|
</div>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
</Dialog>
|
|
);
|
|
}
|