diff --git a/package-lock.json b/package-lock.json index b225239..a52343e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "@types/react": "^19.1.13", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.3", + "baseline-browser-mapping": "^2.9.11", "eslint": "^9.36.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", @@ -3698,9 +3699,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz", - "integrity": "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==", + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4869,9 +4870,9 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index cd08dca..45f66df 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@types/react": "^19.1.13", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.3", + "baseline-browser-mapping": "^2.9.11", "eslint": "^9.36.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", diff --git a/src/App.css b/src/App.css index a241d03..8e078f6 100644 --- a/src/App.css +++ b/src/App.css @@ -248,3 +248,11 @@ button.no-button { text-decoration: underline; } } + +.flex-center-x { + display: flex; + justify-content: center; /* horizontal centering */ + text-align: center; /* center multi-line text */ + width: 100%; /* allow it to stretch */ + flex-wrap: wrap; /* optional: let text wrap naturally */ +} \ No newline at end of file diff --git a/src/components/MultilineTextField.tsx b/src/components/MultilineTextField.tsx new file mode 100644 index 0000000..ad88513 --- /dev/null +++ b/src/components/MultilineTextField.tsx @@ -0,0 +1,75 @@ +import { useEffect, useRef, useState } from "react"; +import styles from "./TextField.module.css"; + +export function MultilineTextField({ + value = "", + setValue, + placeholder, + className, + id, + ariaLabel, + invalid = false, + minRows = 3, +}: { + value: string; + setValue: (value: string) => void; + placeholder?: string; + className?: string; + id?: string; + ariaLabel?: string; + invalid?: boolean; + minRows?: number; +}) { + const [readOnly, setReadOnly] = useState(true); + const [inputValue, setInputValue] = useState(value); + const textareaRef = useRef(null); + + useEffect(() => { + setInputValue(value); + }, [value]); + + // Auto-grow logic + useEffect(() => { + const el = textareaRef.current; + if (!el) return; + + el.style.height = "auto"; + el.style.height = `${el.scrollHeight}px`; + }, [inputValue]); + + const onCommit = () => { + setReadOnly(true); + setValue(inputValue); + }; + + const onKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { + e.preventDefault(); + (e.target as HTMLTextAreaElement).blur(); + } + }; + + return ( +