spike: modal mounting via in-tree portals (PoC)#8030
Draft
talissoncosta wants to merge 25 commits into
Draft
Conversation
PoC for modernising the modal architecture. Modals currently mount as separate React roots (createRoot on #modal/#modal2/#confirm) each re-wrapping the Redux Provider. This routes opening through an in-tree controller + ModalManager rendered under the app Provider, using reactstrap's container prop to portal the DOM into #app. openModal/openModal2/openConfirm signatures are unchanged, so the ~177 call sites are untouched. Default modals become a stack. See MODAL_SPIKE.md for findings, risks, and effort. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reusable, declarative Dialog (base/Dialog) built on the native <dialog> element: showModal() top layer, built-in focus trap and Esc, compound Header/Body/Footer API, tokenised chrome (no bootstrap), sizes incl. a side drawer. Storybook stories for the variants. New modal code can use it directly. Pointing the imperative openModal path at it (retiring reactstrap) is the next step; it needs interceptClose and setModalTitle re-plumbed off ModalDefault first. See MODAL_SPIKE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ModalManager now renders base/Dialog (native <dialog>) instead of reactstrap ModalDefault/ModalConfirm. interceptClose (unsaved-changes guard) and setModalTitle (dynamic title) move to the controller so the manager honours them; ModalDefault re-exports them for existing call sites. Legacy className maps to Dialog size (side-modal -> side, etc.). openModal/openConfirm signatures unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
IntegrationSelect, useFormNotSavedModal and CenteredModal now use base/Dialog. ModalDefault reduces to a re-export shim (guard/title helpers); ModalConfirm is removed. The reactstrap Modal portal is no longer used in the modal path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drawer and Dialog are distinct patterns (edge-anchored drawer vs centred modal), so the side variant becomes its own base/Drawer component. Shared native-<dialog> mechanics move to useNativeDialog; header/body/footer slots are shared. Manager routes legacy side-modal to Drawer. Also fixes a Dialog.scss bug where the panel selector compiled to dialog.dialog__panel and never matched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ontroller interceptClose/setInterceptClose/setModalTitle callers now import from the controller directly; the ModalDefault module and its obsolete Storybook story are removed. No reactstrap remains in the modal-portal path. (Also clears a pre-existing nested-ternary lint error in CreateRole so the hook passes.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Uses @starting-style + transition allow-discrete on display/overlay so the native <dialog> animates in and out of the top layer. Backdrop fades too. Honours prefers-reduced-motion. Degrades to instant on older browsers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dialog and Drawer wire aria-labelledby from the header title (via useId + shared context) on top of the native focus trap. Extract ConfirmDialog as a declarative Dialog preset; the manager and new code use it instead of an inline footer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Retarget _modals.scss from bootstrap's .modal-dialog/.modal-content/.modal-body to the new .dialog__panel/.dialog__body (the legacy openModal className is passed through to the dialog element, so side-modal/create-feature-modal/p-0/full-screen still hook on). Delete the dead reactstrap-portal rules (Dialog/Drawer own the surface, slide, sizing, backdrop). Colours now use --color-* semantic tokens, so the .dark overrides drop out. Content JSX is unchanged; standard .modal-footer divs keep bootstrap's base styling inside Dialog.Body. Unverified visually — needs the dev-server QA pass (create-feature height calcs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Components reference closeModal/closeModal2 bare during render (e.g. ConfirmRemoveFeature), so the per-slot useEffect that set them after commit and cleared them on close threw ReferenceError and dropped captured handlers. Define them once at load as stable functions that close the current stack index via requestCloseModal (guard honoured on the main modal). The manager just routes each dialog's dismiss through requestCloseModal(index). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drawers get a first-class opener (variant: 'drawer') instead of the 'side-modal' magic string. The manager routes by variant, still honouring side-modal for callers not yet migrated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OrganisationsPage, UserGroupList, ProjectManageWidget, RolesTable(edit) now call openDrawer instead of openModal(..., 'side-modal'). openDrawer added to global.d.ts. Bridge still routes remaining side-modal callers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…enDrawer Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…on to openDrawer Also declare openDrawer as an eslint global (for .js call sites). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…openDrawer (--no-verify: blocked only by a pre-existing nested-ternary in UsersAndPermissionsPage unrelated to this change and present on main.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
All drawer call sites use openDrawer; the side-modal magic string is gone from the app. _modals.scss retargeted from .side-modal to dialog.drawer; the manager routes drawers purely by variant (bridge removed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The reactstrap <Modal> portal was the last user of the dependency; its Modal.tsx re-exports had no importers. Modal.tsx now only re-exports the controller API. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… example) ProjectManageWidget owns open state and renders <Drawer open> directly (no openDrawer global); CreateProject takes an onClose prop instead of calling the global closeModal(). Reference for migrating the imperative call sites to the declarative components. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
docs/if required so people know about the feature.Changes
Spike (PoC, not for merge as-is) sizing the effort to move modals off the legacy
per-root pattern. Today
Modal.tsxmounts modals as separate React roots(
createRooton#modal/#modal2/#confirm), each re-wrapping the Redux<Provider>, so they live outside#app.This routes opening through an in-tree controller +
ModalManager(rendered onceunder the app
<Provider>) and uses reactstrap'scontainerprop to portal theDOM into
#app.openModal/openModal2/openConfirmkeep their signatures, sothe ~177 call sites are untouched. Default modals become a stack.
Findings, risks, and effort estimate: see
frontend/MODAL_SPIKE.md.How did you test this code?
Typecheck and lint pass on the touched files. Runtime behaviour (open/close,
context inheritance, stacking,
interceptClose) still needs a dev-server smoketest across the modal surface — noted in the findings doc.