- Extract tab components: JailsTab, ActionsTab, FiltersTab, JailFilesTab, GlobalTab, ServerTab, ConfFilesTab, RegexTesterTab, MapTab, ExportTab - Add form components: JailFileForm, ActionForm, FilterForm - Add AutoSaveIndicator, RegexList, configStyles, and barrel index - ConfigPage now composes these components; greatly reduces file size - Add tests: ConfigPage.test.tsx, useAutoSave.test.ts
104 lines
2.7 KiB
TypeScript
104 lines
2.7 KiB
TypeScript
/**
|
|
* RegexList — editable list of regex patterns.
|
|
*
|
|
* Renders a list of monospace inputs with add/delete controls.
|
|
* Used in jail config panels and the filter form.
|
|
*/
|
|
|
|
import { useCallback, useState } from "react";
|
|
import { Button, Input, Text } from "@fluentui/react-components";
|
|
import { Dismiss24Regular } from "@fluentui/react-icons";
|
|
import { useConfigStyles } from "./configStyles";
|
|
|
|
export interface RegexListProps {
|
|
/** Section label displayed above the list. */
|
|
label: string;
|
|
/** Current list of regex patterns. */
|
|
patterns: string[];
|
|
/** Called when the list changes (add, delete, or edit). */
|
|
onChange: (next: string[]) => void;
|
|
}
|
|
|
|
/**
|
|
* Renders an editable list of regex patterns with add and delete controls.
|
|
*
|
|
* @param props - Component props.
|
|
* @returns JSX element.
|
|
*/
|
|
export function RegexList({
|
|
label,
|
|
patterns,
|
|
onChange,
|
|
}: RegexListProps): React.JSX.Element {
|
|
const styles = useConfigStyles();
|
|
const [newPattern, setNewPattern] = useState("");
|
|
|
|
const handleAdd = useCallback(() => {
|
|
const p = newPattern.trim();
|
|
if (p) {
|
|
onChange([...patterns, p]);
|
|
setNewPattern("");
|
|
}
|
|
}, [newPattern, patterns, onChange]);
|
|
|
|
const handleDelete = useCallback(
|
|
(idx: number) => {
|
|
onChange(patterns.filter((_, i) => i !== idx));
|
|
},
|
|
[patterns, onChange],
|
|
);
|
|
|
|
return (
|
|
<div>
|
|
<Text size={200} weight="semibold">
|
|
{label}
|
|
</Text>
|
|
{patterns.length === 0 && (
|
|
<Text className={styles.infoText} size={200}>
|
|
{" "}
|
|
(none)
|
|
</Text>
|
|
)}
|
|
{patterns.map((p, i) => (
|
|
<div key={i} className={styles.regexItem}>
|
|
<Input
|
|
className={styles.regexInput}
|
|
value={p}
|
|
aria-label={`${label} pattern ${String(i + 1)}`}
|
|
onChange={(_e, d) => {
|
|
const next = [...patterns];
|
|
next[i] = d.value;
|
|
onChange(next);
|
|
}}
|
|
/>
|
|
<Button
|
|
appearance="subtle"
|
|
icon={<Dismiss24Regular />}
|
|
size="small"
|
|
aria-label={`Remove ${label} pattern ${String(i + 1)}`}
|
|
onClick={() => {
|
|
handleDelete(i);
|
|
}}
|
|
/>
|
|
</div>
|
|
))}
|
|
<div className={styles.regexItem}>
|
|
<Input
|
|
className={styles.regexInput}
|
|
placeholder="New pattern…"
|
|
value={newPattern}
|
|
onChange={(_e, d) => {
|
|
setNewPattern(d.value);
|
|
}}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter") handleAdd();
|
|
}}
|
|
/>
|
|
<Button size="small" onClick={handleAdd}>
|
|
Add
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|