Project: doctolib-oxygen · Branch: main · Mode: · Model: claude-haiku-4-5-20251001 Stats: 1 user messages, 7 tool calls, tools used: Glob, Bash, Read. Conversation Turn 1 — User In the Oxygen design system repo at /Users/USER/workspace/doctolib/oxygen, find the Modal component implementation. I need to understand: 1. Where the Modal component source file is located 2. How it handles rendering children when isOpen is false — does it use conditional rendering (return null), portals, CSS visibility/display tricks, or something else? 3. Whether children are mounted/unmounted or just hidden Please read the main Modal component file and report back with the exact rendering logic, file path, and relevant code snippets. Turn 1 — Assistant I'll help you find and analyze the Modal component implementation. Let me start by exploring the repository structure to locate the Modal component. Tools used: - Glob: /[Mm]odal in /Users/USER/workspace/doctolib/oxygen Tool results: → result (ok): packages/b2b/src/overlays/Modal.ts packages/b2b/src/overlays/ModalBody.ts packages/b2b/src/overlays/ModalFooter.ts packages/b2b/src/test-helpers/Modal.ts packages/b2c/src/overlays/Modal.ts packages/b2c/src/overlays/ModalBody.ts packages/b2c/src/overlays/ModalFooter.ts packages/b2c/src/test-helpers/Modal.ts packages/core/public/a11y-components-screenshot/Drawer&Modal/Drawer&Modal-1.png packages/core/src/inlined/react-overlays/ModalManager.js packages/core/src/overlays/Modal/Modal.a11y.mdx package …(truncated, 666 more chars) Tools used: - Bash: find /Users/USER/workspace/doctolib/oxygen -type f -name "[Mm]odal" | head -20 Tool results: → result (ok): /Users/USER/workspace/doctolib/oxygen/packages/core/public/a11y-components-screenshot/Drawer&Modal/Drawer&Modal-1.png /Users/USER/workspace/doctolib/oxygen/packages/core/src/inlined/react-overlays/Modal.jsx /Users/USER/workspace/doctolib/oxygen/packages/core/src/inlined/react-overlays/ModalManager.js /Users/USER/workspace/doctolib/oxygen/packages/core/src/overlays/ModalFooter/ModalFooter.tsx /Users/USER/workspace/doctolib/oxygen/packages/core/src/overlays/ModalFooter/ModalFooter.scss /Users/USER …(truncated, 1313 more chars) Good! I found several Modal component files. The main one appears to be at /Users/USER/workspace/doctolib/oxygen/packages/core/src/overlays/Modal/Modal.tsx. Let me read that file to understand the rendering logic. Tools used: - Read: /Users/USER/workspace/doctolib/oxygen/packages/core/src/overlays/Modal/Modal.tsx Tool results: → result (ok): 1 import { 2 forwardRef, 3 useRef, 4 type ReactElement, 5 type ReactEventHandler, 6 type ReactNode, 7 type SyntheticEvent, 8 } from 'react' 9 import MaterialModal from '@mui/material/Modal' 10 import classNames from 'classnames' 11 import { v4 as uuidv4 } from 'uuid' 12 import { _Transition as Transition } from '../_ModalTransition/_ModalTransition' 13 import { IconButton } from '../../actions/IconButton/v2/IconButton' 14 import { Button } from '../../actions/Button/Button' 15 import …(truncated, 7441 more chars) Now let me check the _ModalTransition component to understand how it handles visibility, since that wraps the content: Tools used: - Read: /Users/USER/workspace/doctolib/oxygen/packages/core/src/overlays/_ModalTransition/_ModalTransition.tsx Tool results: → result (ok): 1 import './_ModalTransition.scss' 2 3 import CSSTransition from 'react-transition-group/CSSTransition' 4 import { forwardRef, useRef } from 'react' 5 import type { ReactNode } from 'react' 6 7 import { MODAL_TRANSITION_DURATION } from '../../constants' 8 9 type ModalTransitionProps = { 10 children: ReactNode 11 fullScreen?: boolean 12 tabIndex?: number 13 } 14 15 type ModalTransitionMaterialProps = ModalTransitionProps & { 16 onEnter?: (node: HTMLElement | null, isAppearing: boolean …(truncated, 1010 more chars) Let me also check the Material-UI Modal documentation or the inlined Modal code to understand how it handles the open prop: Tools used: - Read: /Users/USER/workspace/doctolib/oxygen/packages/core/src/inlined/react-overlays/Modal.jsx Tool results: → result (ok): 1 import React, { cloneElement } from 'react' 2 import { activeElement, contains } from 'dom-helpers' 3 4 import ModalManager from './ModalManager' 5 import Portal from './Portal' 6 import addFocusListener from './utils/addFocusListener' 7 import warning from 'warning' 8 9 const modalManager = new ModalManager() 10 11 / 12 Love them or hate them, provides a solid foundation for creating dialogs, lightboxes, or whatever else. 13 The Modal component renders its children nod …(truncated, 11456 more chars) Perfect! Now let me check the Material-UI Modal implementation to confirm the behavior, and also look at the CSS to see if there are visibility tricks: Tools used: - Read: /Users/USER/workspace/doctolib/oxygen/packages/core/src/overlays/Modal/Modal.scss Tool results: → result (ok): 1 @use '../../style/variables'; 2 3 // FIXME: This is a hack to override the tokens on the monolith where old ones are still loaded 4 .dl-modal.dl-modal { 5 --oxygen-LOCAL-modal-content-background: var(--oxygen-color-primitive-white); 6 --oxygen-LOCAL-modal-header-background: var(--oxygen-color-primitive-white); 7 --oxygen-LOCAL-modal-header-color: var(--oxygen-color-semantic-neutral-prominent-stronger); 8 } 9 10 .MuiModal-root.dl-modal { 11 z-index: variables.$zindex-dialog; 12 } 13 …(truncated, 3523 more chars) Now let me check the ModalTransition CSS to see how the transitions work: Tools used: - Read: /Users/USER/workspace/doctolib/oxygen/packages/core/src/overlays/_ModalTransition/_ModalTransition.scss Tool results: → result (ok): 1 @use '../../style/variables'; 2 3 $duration: 300ms; 4 5 .dl-modal-transition { 6 position: relative; 7 display: inline-block; 8 text-align: left; 9 vertical-align: middle; 10 z-index: variables.$zindex-dialog; 11 12 // On mobile, take all the available space 13 @media (max-width: variables.$screen-sm-min) { 14 width: 100%; 15 } 16 17 &:focus { 18 outline: none; 19 } 20 } 21 22 .dl-modal-transition-fullscreen-appear, 23 .dl-modal-transition-fullscreen-enter { 24 …(truncated, 1050 more chars) Perfect! Now I have all the information I need. Let me create a comprehensive report: Modal Component Analysis Report File Location Main Modal component: /Users/USER/workspace/doctolib/oxygen/packages/core/src/overlays/Modal/Modal.tsx --- 1. How Modal Handles Rendering When isOpen is False The Modal uses Material-UI's Modal component wrapped with a Portal-based approach and conditional mounting. Here's the rendering logic: The Modal passes the show prop directly to Material-UI's MaterialModal:
{children}
Material-UI's Modal (lines 167-227 in Modal.tsx) uses a Portal internally and renders to the DOM regardless of the open state initially. However, the actual visibility is controlled through: 1. The Transition wrapper (_ModalTransition) which uses react-transition-group/CSSTransition 2. The in prop passed to CSSTransition controls visibility via CSS classes --- 2. Rendering Logic: Mounted/Unmounted vs Hidden The behavior depends on the parent Modal implementation: From the inlined react-overlays Modal (lines 358-361): const mountModal = show || (Transition && !this.state.exited) if (!mountModal) { return null } This shows conditional mounting: when show is false AND there's no active transition, the entire Modal returns null (not mounted to DOM). However, during transitions, the Modal stays mounted but uses CSS for visibility: - When show={true}: CSSTransition applies dl-modal-transition-enter-active class, animating from opacity: 0.01 to opacity: 1 with transform - When show={false}: CSSTransition applies dl-modal-transition-exit-active class, animating to opacity: 0.01 and transform: translateY(-25%) - After exit animation completes: The component returns null (unmounts) --- 3. Key Technical Details Component Architecture: - Uses Material-UI's Modal component as the base (from @mui/material/Modal) - Wraps content with _ModalTransition (custom transition wrapper using react-transition-group) - Delegates to Drawer component on mobile if disableDrawerOnMobile is false Transition Duration: - MODAL_TRANSITION_DURATION constant (defined in packages/core/src/constants) - CSS animations: 300ms (from _ModalTransition.scss line 3) CSS Transition Classes Applied (from _ModalTransition.scss): - Enter state: opacity: 0.01 + transform: translateY(-25%) - Enter-active: slides up and fades in over 300ms - Exit state: opacity: 1 - Exit-active: slides down and fades out over 300ms Portal Mounting: - Material-UI renders the Modal content to a portal (typically document.body) - This keeps the modal outside the normal React hierarchy for proper z-indexing and focus management --- Summary Children Behavior: - During open state (show={true}): Children are mounted and visible (CSS opacity: 1) - During close transition (show={false}): Children remain mounted but hidden via CSS (opacity: 0.01, transform applied) for the 300ms animation - After transition completes: Children are unmounted (returns null from Modal) This hybrid approach allows for smooth animations while ensuring unmounted state cleanup.