Summary
Add exit animations to the NormReferenceModal rendered via createPortal in components/RequirementForm.tsx. Currently the modal has enter animations (initial/animate) but no exit animation because AnimatePresence is not used.
Problem
In RequirementForm.tsx (lines 238–306), the modal is conditionally rendered with:
{showCreateNormRef &&
createPortal(
<NormReferenceModal ... />,
document.body,
)}
The NormReferenceModal component (line 468) uses motion.div with initial={{ opacity: 0, scale: 0.95 }} and animate={{ opacity: 1, scale: 1 }}, but since there is no wrapping AnimatePresence, the exit animation never plays — the modal disappears instantly when showCreateNormRef becomes false.
Why this needs care
createPortal returns a ReactPortal, not a motion component. Wrapping the conditional directly in AnimatePresence will not work because Framer Motion needs direct motion.* children to detect unmounts and play exit animations.
AI Implementation Plan
Approach: Always-mounted portal with AnimatePresence inside
Instead of conditionally rendering the portal, always render it and move the conditional inside:
Step 1 — Restructure the portal in RequirementForm.tsx
Replace (lines 238–306):
{showCreateNormRef &&
createPortal(
<NormReferenceModal ... />,
document.body,
)}
With:
{createPortal(
<AnimatePresence>
{showCreateNormRef && (
<NormReferenceModal ... />
)}
</AnimatePresence>,
document.body,
)}
Import AnimatePresence from framer-motion (already imported on line 3).
Step 2 — Add exit props to NormReferenceModal
In the NormReferenceModal component, update the root wrapper and motion.div:
- Change the root
<div> (line 459) to <motion.div> with enter/exit animation for the backdrop:
<motion.div
className="fixed inset-0 z-50 flex items-center justify-center p-4"
ref={overlayRef}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
>
- Add
exit prop to the existing motion.div dialog (line 468):
exit={{ opacity: 0, scale: 0.95 }}
Step 3 — Verify focus management still works
The existing useEffect hooks for focus trapping (lines 404–448) should continue working since the modal component still mounts/unmounts inside AnimatePresence. Verify:
- Focus moves to close button on open
- Focus returns to previously focused element on close
- Tab trapping works during the exit animation window
Step 4 — Update tests
Check tests/unit/ for any RequirementForm tests that assert on portal rendering or modal visibility. Update assertions to account for the always-mounted portal wrapper.
Step 5 — Verify
npx vitest run tests/unit/ --no-color
npm run check
Reference pattern
See ConfirmModal.tsx for the established AnimatePresence + motion.div enter/exit pattern used elsewhere in the codebase.
Files to modify
components/RequirementForm.tsx — restructure portal, add exit props to modal
Risk
- Low: the change is isolated to the modal rendering in one component
- Focus management must be tested since AnimatePresence delays unmount
Summary
Add exit animations to the
NormReferenceModalrendered viacreatePortalincomponents/RequirementForm.tsx. Currently the modal has enter animations (initial/animate) but no exit animation becauseAnimatePresenceis not used.Problem
In
RequirementForm.tsx(lines 238–306), the modal is conditionally rendered with:The
NormReferenceModalcomponent (line 468) usesmotion.divwithinitial={{ opacity: 0, scale: 0.95 }}andanimate={{ opacity: 1, scale: 1 }}, but since there is no wrappingAnimatePresence, the exit animation never plays — the modal disappears instantly whenshowCreateNormRefbecomesfalse.Why this needs care
createPortalreturns aReactPortal, not amotioncomponent. Wrapping the conditional directly inAnimatePresencewill not work because Framer Motion needs directmotion.*children to detect unmounts and play exit animations.AI Implementation Plan
Approach: Always-mounted portal with AnimatePresence inside
Instead of conditionally rendering the portal, always render it and move the conditional inside:
Step 1 — Restructure the portal in
RequirementForm.tsxReplace (lines 238–306):
With:
Import
AnimatePresencefromframer-motion(already imported on line 3).Step 2 — Add exit props to
NormReferenceModalIn the
NormReferenceModalcomponent, update the root wrapper andmotion.div:<div>(line 459) to<motion.div>with enter/exit animation for the backdrop:exitprop to the existingmotion.divdialog (line 468):Step 3 — Verify focus management still works
The existing
useEffecthooks for focus trapping (lines 404–448) should continue working since the modal component still mounts/unmounts insideAnimatePresence. Verify:Step 4 — Update tests
Check
tests/unit/for anyRequirementFormtests that assert on portal rendering or modal visibility. Update assertions to account for the always-mounted portal wrapper.Step 5 — Verify
Reference pattern
See
ConfirmModal.tsxfor the established AnimatePresence + motion.div enter/exit pattern used elsewhere in the codebase.Files to modify
components/RequirementForm.tsx— restructure portal, add exit props to modalRisk