105 lines
3.1 KiB
TypeScript
105 lines
3.1 KiB
TypeScript
|
|
/**
|
||
|
|
* EvidenceFormBody — the shared "citation + commentary" editor.
|
||
|
|
*
|
||
|
|
* One form drives both:
|
||
|
|
* - the *capture* flow (creating evidence from a fresh selection
|
||
|
|
* inside `InlineCaptureForm`), and
|
||
|
|
* - the *edit* flow (modifying an existing evidence card inside
|
||
|
|
* `EvidenceSidebar`).
|
||
|
|
*
|
||
|
|
* Keeping a single component means changes to the field layout, hint
|
||
|
|
* placement, or save-button copy land in one file and apply to both
|
||
|
|
* surfaces. Callers control the labels and any badge/helper text.
|
||
|
|
*/
|
||
|
|
|
||
|
|
import type { CSSProperties, ReactNode } from "react";
|
||
|
|
|
||
|
|
export interface EvidenceFormBodyProps {
|
||
|
|
readonly quote: string;
|
||
|
|
readonly commentary: string;
|
||
|
|
onChangeQuote(next: string): void;
|
||
|
|
onChangeCommentary(next: string): void;
|
||
|
|
onSave(): void;
|
||
|
|
onCancel(): void;
|
||
|
|
/** Save-button label, defaults to "Save". */
|
||
|
|
readonly saveLabel?: string;
|
||
|
|
/** Cancel-button label, defaults to "Cancel". */
|
||
|
|
readonly cancelLabel?: string;
|
||
|
|
/** Short caption rendered at the top of the form, e.g. a selector
|
||
|
|
* count badge or "Editing". */
|
||
|
|
readonly badge?: ReactNode;
|
||
|
|
/** Inline note shown under the buttons (e.g. "won't move the
|
||
|
|
* marked passage" when editing an existing item). */
|
||
|
|
readonly helper?: ReactNode;
|
||
|
|
/** data-testid prefix for the input + button hooks. */
|
||
|
|
readonly testidPrefix: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function EvidenceFormBody(p: EvidenceFormBodyProps) {
|
||
|
|
const saveLabel = p.saveLabel ?? "Save";
|
||
|
|
const cancelLabel = p.cancelLabel ?? "Cancel";
|
||
|
|
return (
|
||
|
|
<div style={{ padding: 8, fontSize: 12 }}>
|
||
|
|
{p.badge && (
|
||
|
|
<div style={{ marginBottom: 6, fontWeight: 600 }}>{p.badge}</div>
|
||
|
|
)}
|
||
|
|
<label style={labelStyle}>Citation text</label>
|
||
|
|
<textarea
|
||
|
|
value={p.quote}
|
||
|
|
onChange={(e) => p.onChangeQuote(e.target.value)}
|
||
|
|
data-testid={`${p.testidPrefix}-quote`}
|
||
|
|
rows={3}
|
||
|
|
style={{
|
||
|
|
...textareaStyle,
|
||
|
|
fontStyle: "italic",
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
<label style={labelStyle}>Commentary</label>
|
||
|
|
<textarea
|
||
|
|
value={p.commentary}
|
||
|
|
onChange={(e) => p.onChangeCommentary(e.target.value)}
|
||
|
|
data-testid={`${p.testidPrefix}-commentary`}
|
||
|
|
rows={2}
|
||
|
|
placeholder="(optional)"
|
||
|
|
style={textareaStyle}
|
||
|
|
/>
|
||
|
|
<div style={{ display: "flex", gap: 6, alignItems: "center" }}>
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
onClick={p.onSave}
|
||
|
|
data-testid={`${p.testidPrefix}-save`}
|
||
|
|
style={{ fontSize: 12, padding: "4px 10px" }}
|
||
|
|
>
|
||
|
|
{saveLabel}
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
onClick={p.onCancel}
|
||
|
|
data-testid={`${p.testidPrefix}-cancel`}
|
||
|
|
style={{ fontSize: 12, padding: "4px 10px" }}
|
||
|
|
>
|
||
|
|
{cancelLabel}
|
||
|
|
</button>
|
||
|
|
{p.helper && (
|
||
|
|
<span style={{ fontSize: 11, color: "#888" }}>{p.helper}</span>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const labelStyle: CSSProperties = {
|
||
|
|
display: "block",
|
||
|
|
color: "#666",
|
||
|
|
fontSize: 11,
|
||
|
|
marginBottom: 2,
|
||
|
|
};
|
||
|
|
|
||
|
|
const textareaStyle: CSSProperties = {
|
||
|
|
width: "100%",
|
||
|
|
boxSizing: "border-box",
|
||
|
|
fontSize: 12,
|
||
|
|
padding: 4,
|
||
|
|
marginBottom: 6,
|
||
|
|
};
|