125 lines
3.9 KiB
TypeScript
125 lines
3.9 KiB
TypeScript
import {useEffect, useState} from "react";
|
|
import styles from "./TextField.module.css";
|
|
|
|
/**
|
|
* A styled text input that updates its value **in real time** at every keystroke.
|
|
*
|
|
* Automatically toggles between read-only and editable modes to integrate with
|
|
* drag-based UIs (like React Flow). Calls `onCommit` when editing is completed.
|
|
*
|
|
* @param props - Component properties.
|
|
* @param props.value - The current text input value.
|
|
* @param props.setValue - Callback invoked on every keystroke to update the value.
|
|
* @param props.onCommit - Callback invoked when editing is finalized (on blur or Enter).
|
|
* @param props.placeholder - Optional placeholder text displayed when the input is empty.
|
|
* @param props.className - Optional additional CSS class names.
|
|
* @param props.id - Optional unique HTML `id` for the input element.
|
|
* @param props.ariaLabel - Optional ARIA label for accessibility.
|
|
* @param props.invalid - If true, applies error styling to indicate invalid input.
|
|
*
|
|
* @returns A styled `<input>` element that updates its value in real time.
|
|
*/
|
|
export function RealtimeTextField({
|
|
value = "",
|
|
setValue,
|
|
onCommit,
|
|
placeholder,
|
|
className,
|
|
id,
|
|
ariaLabel,
|
|
invalid = false,
|
|
} : {
|
|
value: string,
|
|
setValue: (value: string) => void,
|
|
onCommit: () => void,
|
|
placeholder?: string,
|
|
className?: string,
|
|
id?: string,
|
|
ariaLabel?: string,
|
|
invalid?: boolean,
|
|
}) {
|
|
/** Tracks whether the input is currently read-only (for drag compatibility). */
|
|
const [readOnly, setReadOnly] = useState(true);
|
|
|
|
/** Finalizes editing and calls `onCommit` when the user exits the field. */
|
|
const updateData = () => {
|
|
setReadOnly(true);
|
|
onCommit();
|
|
};
|
|
|
|
/** Handles the Enter key — commits the input by triggering a blur event. */
|
|
const updateOnEnter = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
if (event.key === "Enter")
|
|
(event.target as HTMLInputElement).blur(); };
|
|
|
|
return <input
|
|
type={"text"}
|
|
placeholder={placeholder}
|
|
value={value}
|
|
onChange={(e) => setValue(e.target.value)}
|
|
onFocus={() => setReadOnly(false)}
|
|
onBlur={updateData}
|
|
onKeyDown={updateOnEnter}
|
|
readOnly={readOnly}
|
|
id={id}
|
|
// ReactFlow uses the "drag" / "nodrag" classes to enable / disable dragging of nodes
|
|
className={`${readOnly ? "drag" : "nodrag"} ${styles.textField} ${invalid ? styles.invalid : ""} ${className}`}
|
|
aria-label={ariaLabel}
|
|
/>;
|
|
}
|
|
|
|
/**
|
|
* A styled text input that updates its value **only on commit** (when the user
|
|
* presses Enter or clicks outside the input).
|
|
*
|
|
* Internally wraps `RealtimeTextField` and buffers input changes locally,
|
|
* calling `setValue` only once editing is complete.
|
|
*
|
|
* @param props - Component properties.
|
|
* @param props.value - The current text input value.
|
|
* @param props.setValue - Callback invoked when the user commits the change.
|
|
* @param props.placeholder - Optional placeholder text displayed when the input is empty.
|
|
* @param props.className - Optional additional CSS class names.
|
|
* @param props.id - Optional unique HTML `id` for the input element.
|
|
* @param props.ariaLabel - Optional ARIA label for accessibility.
|
|
* @param props.invalid - If true, applies error styling to indicate invalid input.
|
|
*
|
|
* @returns A styled `<input>` element that updates its parent state only on commit.
|
|
*/
|
|
export function TextField({
|
|
value = "",
|
|
setValue,
|
|
placeholder,
|
|
className,
|
|
id,
|
|
ariaLabel,
|
|
invalid = false,
|
|
} : {
|
|
value: string,
|
|
setValue: (value: string) => void,
|
|
placeholder?: string,
|
|
className?: string,
|
|
id?: string,
|
|
ariaLabel?: string,
|
|
invalid?: boolean,
|
|
}) {
|
|
const [inputValue, setInputValue] = useState(value);
|
|
|
|
useEffect(() => {
|
|
setInputValue(value);
|
|
}, [value]);
|
|
|
|
const onCommit = () => setValue(inputValue);
|
|
|
|
return <RealtimeTextField
|
|
placeholder={placeholder}
|
|
value={inputValue}
|
|
setValue={setInputValue}
|
|
onCommit={onCommit}
|
|
id={id}
|
|
className={className}
|
|
ariaLabel={ariaLabel}
|
|
invalid={invalid}
|
|
/>;
|
|
}
|