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, <Modal/> 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:
<MaterialModal
open={show}
closeAfterTransition
// ... other props
>
<Transition fullScreen={size === 'fullscreen'} in={show}>
<div className='dl-modal-content' /* ... */>
{children}
</div>
</Transition>
</MaterialModal>
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:
- The Transition wrapper (
_ModalTransition) which usesreact-transition-group/CSSTransition - The
inprop 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 appliesdl-modal-transition-enter-activeclass, animating fromopacity: 0.01toopacity: 1with transform - When
show={false}: CSSTransition appliesdl-modal-transition-exit-activeclass, animating toopacity: 0.01andtransform: 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.