- Add useMobileScrollLock hook using position:fixed body lock (Bootstrap/ Headless UI/Stripe pattern) to prevent iOS Safari from shifting the document and visualViewport when an input inside a fixed-position modal is focused. Wire into 15 input-bearing modals plus ChatView, replacing ChatView's inline body-overflow effect. - Widen computeBuildVersion in vite.config.ts to hash the entire app/ tree so the version-check poll actually notices rebuilds (FN-3333 follow-up; previously only main.tsx and package.json were hashed). - ChatView: on input blur, suppress keyboard-aware sizing for 450ms while reserving mobile-nav-bar space, so the composer snaps to its final height in one move instead of crawling down with iOS's keyboard slide and then jumping again when the nav bar reappears. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
75 lines
2.4 KiB
TypeScript
75 lines
2.4 KiB
TypeScript
import "./TodoModal.css";
|
|
import { useEffect } from "react";
|
|
import { ListChecks, X } from "lucide-react";
|
|
import { useOverlayDismiss } from "../hooks/useOverlayDismiss";
|
|
import { useMobileKeyboard } from "../hooks/useMobileKeyboard";
|
|
import { useMobileScrollLock } from "../hooks/useMobileScrollLock";
|
|
import { useViewportMode } from "./Header";
|
|
import { TodoView } from "./TodoView";
|
|
|
|
interface TodoModalProps {
|
|
isOpen?: boolean;
|
|
onClose: () => void;
|
|
projectId?: string;
|
|
addToast: (message: string, type?: "success" | "error" | "info") => void;
|
|
onPlanningMode?: (initialPlan: string) => void;
|
|
}
|
|
|
|
export function TodoModal({ onClose, projectId, addToast, onPlanningMode }: TodoModalProps) {
|
|
const overlayDismissProps = useOverlayDismiss(onClose);
|
|
const mode = useViewportMode();
|
|
const isMobile = mode === "mobile";
|
|
const { keyboardOverlap, viewportHeight, viewportOffsetTop, keyboardOpen } = useMobileKeyboard({
|
|
enabled: isMobile,
|
|
});
|
|
useMobileScrollLock(isMobile);
|
|
|
|
const modalKeyboardStyle: React.CSSProperties =
|
|
keyboardOpen
|
|
? ({
|
|
"--keyboard-overlap": `${keyboardOverlap}px`,
|
|
"--vv-offset-top": `${viewportOffsetTop}px`,
|
|
...(viewportHeight !== null ? { "--vv-height": `${viewportHeight}px` } : {}),
|
|
} as React.CSSProperties)
|
|
: {};
|
|
|
|
useEffect(() => {
|
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
if (event.key === "Escape") {
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
document.addEventListener("keydown", handleKeyDown);
|
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
}, [onClose]);
|
|
|
|
return (
|
|
<div className="modal-overlay open" {...overlayDismissProps} role="dialog" aria-modal="true">
|
|
<div className="modal todo-modal" style={modalKeyboardStyle}>
|
|
<div className="modal-header todo-modal-header">
|
|
<div className="todo-modal-header-title">
|
|
<ListChecks size={18} />
|
|
<div>
|
|
<h2>Todos</h2>
|
|
<p>Manage reusable todo lists for your project.</p>
|
|
</div>
|
|
</div>
|
|
<button className="modal-close" onClick={onClose} aria-label="Close">
|
|
<X size={20} />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="todo-modal-body">
|
|
<TodoView
|
|
projectId={projectId}
|
|
addToast={addToast}
|
|
onPlanningMode={onPlanningMode}
|
|
onClose={onClose}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|