Skip to content

Make all Studio modals draggable by their header - #3931

Open
vin0401 wants to merge 2 commits into
2026.xfrom
feat/draggable-modals
Open

Make all Studio modals draggable by their header#3931
vin0401 wants to merge 2 commits into
2026.xfrom
feat/draggable-modals

Conversation

@vin0401

@vin0401 vin0401 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

What

Resolves https://github.com/pimcore/product-management/issues/1259

Makes every Studio modal draggable by its header/title, via a single shared drag implementation.

Shared core

  • useDraggableModal hook — wraps modal content in react-draggable using the standard antd modalRender recipe: drag handle scoped to .ant-modal-header / .ant-modal-confirm-title via a CSS selector (close button, buttons, links and form controls in the header are excluded from initiating a drag), viewport-clamped bounds recomputed on drag start, nodeRef (React 18/19 safe), and a controlled position that resets to centre each time the modal opens.
  • DraggableModalRender / withDraggableModalRender — component + helper for the imperative modal API, composing with any caller-provided modalRender.

Two wiring surfaces

  • Declarative — base Modal gains a draggable?: boolean prop (default true), so all existing Modal usages inherit dragging. A caller-supplied modalRender always wins and disables the built-in behaviour. Mask / centering / focus-trap are untouched.
  • ImperativeuseStudioModal wraps confirm/info/success/error/warning, so useFormModal and useAlertModal dialogs become draggable too.

Consolidation

  • WindowModal now consumes the shared hook instead of its own copy of the drag logic (keeps its mask={false} + iframe getPopupContainer behaviour).
  • Remaining direct App.useApp().modal consumers migrated to useStudioModal so their dialogs are draggable as well.
  • The cross-iframe registrar (app-loader / modalApi) intentionally keeps the raw modal instance — child iframes re-wrap it via useStudioModal, so wrapping there would nest two Draggables.

🤖 Generated with Claude Code

Introduce a shared `useDraggableModal` hook that wraps modal content in
react-draggable using the standard antd `modalRender` recipe (header/title
drag handle via a CSS selector, viewport-clamped bounds, nodeRef, controlled
position reset to centre on each open).

Wire it into both modal surfaces:
- Declarative: base `Modal` gains a `draggable` prop (default true); a
  caller-provided `modalRender` still takes precedence.
- Imperative: `useStudioModal` wraps confirm/info/success/error/warning so
  the form and alert modals become draggable, composing with any existing
  modalRender.

Refactor `WindowModal` to consume the shared hook (single drag
implementation) and migrate the remaining direct `App.useApp().modal`
consumers to `useStudioModal` so their dialogs are draggable too. The
cross-iframe registrar (app-loader / modalApi) intentionally keeps the raw
instance to avoid double-wrapping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 24, 2026 07:51
@vin0401 vin0401 added this to the 2026.3.0 milestone Jul 24, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Verdict: Needs changes. The PR centralizes draggable behavior for shared declarative and imperative modals.

Changes:

  • Adds a shared draggable-modal hook and imperative renderer.
  • Enables dragging by default in the shared Modal.
  • Migrates imperative modal consumers to useStudioModal.

Assessment:

  • Root cause/boundary: Correctly targets shared modal infrastructure.
  • Call-site coverage: Incomplete; direct Ant Design Modal consumers remain non-draggable.
  • Compatibility: Additive API, with custom modalRender preserved.
  • Tests/docs: Inline documentation added, but no regression tests or changelog.
  • Risk: Iframe bounds use the child viewport despite rendering in the parent document.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
assets/js/src/sdk/components/index.ts Exports draggable-modal APIs.
assets/js/src/core/modules/perspectives/hooks/use-perspectives.tsx Migrates local modal access.
assets/js/src/core/modules/element/hooks/use-element-actions-menu.tsx Migrates local modal access.
assets/js/src/core/modules/document/actions/site/use-site-actions.tsx Migrates local modal access.
assets/js/src/core/modules/document/actions/site/provider/site-modal-provider.tsx Migrates local modal access.
assets/js/src/core/modules/document/actions/paste/use-paste.tsx Migrates local modal access.
assets/js/src/core/modules/document/actions/add-page/use-add-document.tsx Migrates local modal access.
assets/js/src/core/modules/classification-store-config/components/store-editor/tabs/keys-tab.tsx Migrates local modal access.
assets/js/src/core/modules/classification-store-config/components/store-editor/tabs/groups-tab.tsx Migrates local modal access.
assets/js/src/core/modules/classification-store-config/components/store-editor/tabs/collections-tab.tsx Migrates local modal access.
assets/js/src/core/modules/classification-store-config/components/sidebar/sidebar.tsx Migrates local modal access.
assets/js/src/core/components/modal/window-modal/window-modal.tsx Reuses the shared drag hook.
assets/js/src/core/components/modal/modal.tsx Enables declarative dragging by default.
assets/js/src/core/components/modal/hooks/use-studio-modal.tsx Wraps imperative modal methods.
assets/js/src/core/components/modal/hooks/use-draggable-modal.tsx Implements shared drag state and bounds.
assets/js/src/core/components/modal/hooks/use-draggable-modal.styles.tsx Adds drag cursor styling.
assets/js/src/core/components/modal/hooks/draggable-modal-render.tsx Composes imperative modal renderers.
assets/js/src/core/components/modal-upload/hooks/use-upload-conflict-modal.tsx Migrates upload-conflict dialogs.

}, [open])

const onStart = useCallback((_event: DraggableEvent, uiData: DraggableData): void => {
const { clientWidth, clientHeight } = window.document.documentElement
}

export const Modal = ({ iconName, size = 'M', limitContentHeight, className, title, children, styles: stylesProp, ...props }: IModalProps): React.JSX.Element => {
export const Modal = ({ iconName, size = 'M', limitContentHeight, className, title, children, styles: stylesProp, draggable = true, modalRender: modalRenderProp, ...props }: IModalProps): React.JSX.Element => {
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants