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.