Skip to content

Add ProModal component (#9503)#12420

Open
msynk wants to merge 5 commits into
bitfoundation:developfrom
msynk:9503-blazorui-extras-promodal-ii
Open

Add ProModal component (#9503)#12420
msynk wants to merge 5 commits into
bitfoundation:developfrom
msynk:9503-blazorui-extras-promodal-ii

Conversation

@msynk

@msynk msynk commented Jun 1, 2026

Copy link
Copy Markdown
Member

closes #9503

Summary by CodeRabbit

  • New Features

    • Added a ProModal component with rich configuration (positioning, sizing modes, draggable support, header/footer slots, overlay/blocking options, ARIA support, and open/close controls).
    • New demos and demo styling showcasing ProModal and ModalService usage.
  • Bug Fixes / Behavior

    • Improved overlay and dismiss handling and ARIA attribute behavior for modals.
  • Chores

    • Service and interop drag-drop behavior reorganized.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR introduces a new BitProModal component to the Bit.BlazorUI Extras library. The component wraps BitModal and adds 25+ configurable parameters for positioning, sizing, visibility state, header/footer/body content slots, close button behavior, and event callbacks. It includes complete demo coverage with 11 examples and integrates into the app navigation.

Changes

BitProModal Component

Layer / File(s) Summary
Component styling contract
src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModalClassStyles.cs
BitProModalClassStyles class extends BitModalClassStyles with six nullable properties to customize CSS for header, close button, body, and footer regions.
Component parameters and lifecycle
src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.razor.cs
BitProModal class defines 25+ [Parameter] properties controlling positioning (AbsolutePosition, Position, DragElementSelector), sizing (FullSize, FullWidth, FullHeight), visibility/behavior (IsOpen, Draggable, Blocking, Modeless, ModeFull, AutoToggleScroll), content slots (Header, HeaderText, Body, ChildContent, Footer, FooterText), close button (CloseIcon, CloseIconName, ShowCloseButton), and callbacks (OnOpen, OnDismiss, OnOverlayClick). Implements Open() and Close() methods that trigger OnOpen exactly once per opening via OnAfterRenderAsync.
Component markup
src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.razor
Razor markup wraps BitModal and conditionally renders header (with optional close button using icon CSS classes), body (from Body or ChildContent), and footer (from Footer or FooterText), all connected to code-behind parameters and lifecycle.
Component styling and asset registration
src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.scss, src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
BitProModal.scss defines BEM-style rules for the modal structure (flex container, overlay background), sticky header/footer regions (with z-index layering, padding, font weight), close button interactive states (hover/active colors), and scrollable body. Stylesheet is imported in extra-components.scss.
JS drag/drop interop
src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.ts, src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModalJsRuntimeExtensions.cs
Adds ProModal TypeScript helpers to setup/remove pointer-based drag behavior and corresponding IJSRuntime extensions used by BitProModal to wire drag operations.
Dialog & Modal surface updates
src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Dialog/*, src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/*
Dialog drag-drop interop updated to Dialog-specific JS functions. BitModal API changed: adds AriaModal and ShowOverlay, removes Modeless and multiple scroll/position-related parameters; overlay-click handling and class registration adjusted; BitModalParameters merge simplified accordingly.
Service & DI changes
src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs, src/BlazorUI/Bit.BlazorUI/Extensions/IBitBlazorUIServiceCollectionExtensions.cs
Extras registration now calls AddBitBlazorUIServices(trySingleton) and conditionally registers BitModalService as singleton when requested; AddBitBlazorUIServices gained optional trySingleton parameter.
Demo pages & navigation
src/BlazorUI/Demo/Client/.../ProModal/*, .../ModalService/*, Pages/Home/ComponentsSection.razor, Shared/MainLayout.*, compilerconfig.json
Adds /components/promodal demo with 11 examples and demo SCSS; moves/updates ModalService demo and its compilerconfig mapping; adds navigation links for ProModal and ModalService; MainLayout modal container default parameters simplified.
Tests
src/BlazorUI/Tests/.../ProModal/BitProModalTests.cs, src/BlazorUI/Tests/.../Surfaces/Modal/*
Adds BitProModal unit tests covering overlay/aria behavior, blocking overlay interactions, OnDismiss firing, ARIA ID forwarding, and position-class mapping; updates ModalService and Modal tests/namespaces to reflect API and demo changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A modal so pro, with parameters galore!
Open and close with style evermore,
Headers and footers in perfect array,
Draggable and placed where you wish it to stay,
✨ Hops of joy for a polished display!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add ProModal component (#9503)' clearly and concisely describes the main change: adding the ProModal component to the BlazorUI Extras project, directly addressing the objective in issue #9503.
Linked Issues check ✅ Passed The PR successfully adds BitProModal component to the Extras project with complete implementation, including component files, styling, tests, and demo pages, fully addressing issue #9503's requirement.
Out of Scope Changes check ✅ Passed Changes to BitModal, BitDialog, BitModalService, and their related test files represent necessary refactoring to extract shared functionality that ProModal depends on, supporting the main objective rather than introducing unrelated changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.razor.cs`:
- Around line 23-26: The XML doc for the Blocking parameter is incorrect: it
currently says “Whether the Modal can be light dismissed…”, but Blocking
actually prevents light-dismiss when true (overlay click handler returns early
when ModalParameters.Blocking is true). Update the summary for the Blocking
property in BitProModal (and the duplicated summaries in BitProModalDemo and any
BitModal references) to state that when true the modal cannot be light-dismissed
by clicking the overlay; when false overlay clicks allow dismissal. Ensure the
wording clearly indicates the boolean semantics (true = prevents light-dismiss,
false = allows it).

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.scss`:
- Line 1: In BitProModal.scss update the `@import` to omit the .scss extension
(change the import of 'functions.scss' to 'functions') to satisfy the stylelint
rule scss/load-partial-extension; locate the `@import` line in BitProModal.scss
that references '../../../Bit.BlazorUI/Styles/functions.scss' and remove the
'.scss' suffix (or alternatively add/configure a stylelint suppression/baseline
for this rule if you intend to keep extensions across Extras files).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 839c5e64-71a6-48d0-8212-86f5b4be13b0

📥 Commits

Reviewing files that changed from the base of the PR and between 046d116 and 082ef02.

📒 Files selected for processing (10)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModalClassStyles.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProModal/BitProModalDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProModal/BitProModalDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProModal/BitProModalDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Home/ComponentsSection.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs

@msynk

msynk commented Jun 9, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai re-review

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

@msynk: I'll re-review the PR now.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Dialog/BitDialog.ts (1)

59-65: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

removeDragDrop can leak listener state when the drag element is already unmounted.

When querySelector returns null, the function returns before removing document listeners and deleting the registry entry.

Suggested patch
 public static removeDragDrop(id: string, dragElementSelector: string) {
     const listeners = Dialog._dragDropListeners[id];
     if (!listeners) return;

     const dragElement = document.querySelector(dragElementSelector)! as HTMLElement;
-    if (!dragElement) return;
-
-    dragElement.removeEventListener('pointerdown', listeners['pointerdown']);
-    dragElement.style.cursor = '';
-    dragElement.classList.remove('bit-mdl-nta');
+    if (dragElement) {
+        dragElement.removeEventListener('pointerdown', listeners['pointerdown']);
+        dragElement.style.cursor = '';
+        dragElement.classList.remove('bit-mdl-nta');
+    }

     document.removeEventListener('pointermove', listeners['pointermove']);
     document.removeEventListener('pointerup', listeners['pointerup']);

     delete listeners['pointerdown'];
     delete listeners['pointermove'];
     delete listeners['pointerup'];
     delete Dialog._dragDropListeners[id];
 }

Also applies to: 70-80

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Dialog/BitDialog.ts` around
lines 59 - 65, removeDragDrop currently exits early if
document.querySelector(dragElementSelector) returns null, leaking the entry in
Dialog._dragDropListeners and leaving document listeners attached; instead,
always read Dialog._dragDropListeners[id] and, even when dragElement is null,
remove the stored document-level listeners (using the listener functions from
the listeners object) and then delete Dialog._dragDropListeners[id]; only skip
element-specific cleanup when dragElement is null. Apply the same fix to the
other branch/duplicate logic around lines 70-80 that also assumes the element
exists.
🧹 Nitpick comments (4)
src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.razor.cs (1)

246-285: 💤 Low value

Consider adding clarifying comments for _offsetTop and AbsolutePosition logic.

The lifecycle logic is correct but subtle:

  1. Line 259 sets _offsetTop = 0 before calling ToggleScroll. This appears redundant since ToggleScroll (lines 341, 345) overwrites _offsetTop, but it's actually a guard for when AutoToggleScroll is false (line 337 early return).

  2. Lines 263-267 reset and re-render only for AbsolutePosition. This ensures the top offset style (registered on line 243) is applied after ToggleScroll calculates the scroll offset.

📝 Suggested clarifying comments
-                _offsetTop = 0;
+                // Reset offset; will be recalculated by ToggleScroll if AutoToggleScroll is enabled
+                _offsetTop = 0;

                 await ToggleScroll(true);
                 
                 if (AbsolutePosition)
                 {
+                    // Re-render to apply the calculated top offset style for absolute positioning
                     StyleBuilder.Reset();
                     StateHasChanged();
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.razor.cs`
around lines 246 - 285, Add brief clarifying comments inside OnAfterRenderAsync
around the _offsetTop and AbsolutePosition logic: comment why _offsetTop = 0 is
set before calling ToggleScroll (it's a guard for the case when AutoToggleScroll
is false and ToggleScroll will not recalc _offsetTop), and comment why
StyleBuilder.Reset() and StateHasChanged() are invoked only when
AbsolutePosition is true (to ensure the computed top-offset style, which
ToggleScroll may have updated, is applied after render). Reference the symbols
_offsetTop, ToggleScroll, AutoToggleScroll, AbsolutePosition,
StyleBuilder.Reset, and OnAfterRenderAsync when placing the comments.
src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModalClassStyles.cs (1)

3-34: ⚡ Quick win

Consider adding a Merge method for BitProModalClassStyles.

The base BitModalClassStyles provides a static Merge method, but it only merges the base properties (Root, Overlay, Content). When BitProModalClassStyles instances need to be merged, the derived properties (HeaderContainer, Header, CloseButton, CloseIcon, Body, Footer) will be lost.

📦 Proposed Merge method
+    public static BitProModalClassStyles Merge(BitProModalClassStyles? classStyles1, BitProModalClassStyles? classStyles2)
+    {
+        var baseStyles = BitModalClassStyles.Merge(classStyles1, classStyles2);
+        return new BitProModalClassStyles
+        {
+            Root = baseStyles.Root,
+            Overlay = baseStyles.Overlay,
+            Content = baseStyles.Content,
+            HeaderContainer = classStyles1?.HeaderContainer ?? classStyles2?.HeaderContainer,
+            Header = classStyles1?.Header ?? classStyles2?.Header,
+            CloseButton = classStyles1?.CloseButton ?? classStyles2?.CloseButton,
+            CloseIcon = classStyles1?.CloseIcon ?? classStyles2?.CloseIcon,
+            Body = classStyles1?.Body ?? classStyles2?.Body,
+            Footer = classStyles1?.Footer ?? classStyles2?.Footer
+        };
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModalClassStyles.cs`
around lines 3 - 34, The BitProModalClassStyles class lacks a Merge method so
derived properties (HeaderContainer, Header, CloseButton, CloseIcon, Body,
Footer) are not preserved when merging; add a static
Merge(BitProModalClassStyles? first, BitProModalClassStyles? second) that calls
BitModalClassStyles.Merge for base fields then merges each derived string
property (choose non-null/longer/concatenate as project convention) into the
result, ensuring the returned BitProModalClassStyles contains merged values for
Root/Overlay/Content plus HeaderContainer, Header, CloseButton, CloseIcon, Body,
and Footer.
src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.scss (1)

37-110: ⚖️ Poor tradeoff

Significant duplication in positioning classes.

Six pairs of positioning classes are identical, resulting in 36+ lines of duplicate CSS:

  • .bit-mdl-tlf (TopLeft) = .bit-mdl-tst (TopStart)
  • .bit-mdl-trg (TopRight) = .bit-mdl-ten (TopEnd)
  • .bit-mdl-clf (CenterLeft) = .bit-mdl-cst (CenterStart)
  • .bit-mdl-crg (CenterRight) = .bit-mdl-cen (CenterEnd)
  • .bit-mdl-blf (BottomLeft) = .bit-mdl-bst (BottomStart)
  • .bit-mdl-brg (BottomRight) = .bit-mdl-ben (BottomEnd)

Additionally, this causes incorrect RTL behavior: absolute directions (Left/Right) currently use flexbox flex-start/flex-end, which adapt to the dir attribute. In RTL mode, TopLeft will incorrectly position on the right. Absolute directions should use fixed positioning (left: 0, right: 0) while logical directions (Start/End) should continue using flexbox.

🎨 Refactor approach

For absolute directions, replace flexbox with explicit positioning:

.bit-mdl-tlf {
    top: 0;
    left: 0;
}

.bit-mdl-trg {
    top: 0;
    right: 0;
}

Keep logical directions using flexbox (they correctly adapt to dir):

.bit-mdl-tst {
    align-items: flex-start;
    justify-content: flex-start;
}

This eliminates duplication and fixes RTL behavior for absolute directions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.scss` around
lines 37 - 110, Duplicate positioning classes cause redundant rules and break
RTL: replace the absolute-direction classes (.bit-mdl-tlf, .bit-mdl-trg,
.bit-mdl-clf, .bit-mdl-crg, .bit-mdl-blf, .bit-mdl-brg) so they use explicit
positioning (top/left/top/right/bottom/left/bottom/right) instead of flexbox,
and keep the logical-direction classes (.bit-mdl-tst, .bit-mdl-ten,
.bit-mdl-cst, .bit-mdl-cen, .bit-mdl-bst, .bit-mdl-ben) using flexbox
(align-items/justify-content) to preserve RTL behavior; remove the duplicated
flexbox rules from the absolute classes and consolidate identical rules to avoid
repetition.
src/BlazorUI/Bit.BlazorUI/Extensions/IBitBlazorUIServiceCollectionExtensions.cs (1)

10-17: Consider documenting singleton usage constraints.

When trySingleton=true, BitModalService is registered as a singleton with a single _container field. If multiple BitModalContainer instances exist in the application, the last container to call InitContainer will become the active container, potentially causing persistent modals queued before that point to be rendered in an unexpected location.

Applications using trySingleton=true should ensure only one BitModalContainer exists, or document this behavior for developers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Bit.BlazorUI/Extensions/IBitBlazorUIServiceCollectionExtensions.cs`
around lines 10 - 17, The BitModalService singleton registration (controlled by
trySingleton) uses a single _container and when multiple BitModalContainer
instances call InitContainer the last caller wins; update documentation to state
that if you pass trySingleton=true you must ensure only one BitModalContainer
exists (or explain the last-InitContainer-wins behavior and that scoped
registration avoids it), and add this note to the public API surface — e.g., XML
docs on IBitBlazorUIServiceCollectionExtensions.AddBitBlazorUI/BitModalService
registration method and/or the BitModalService class mentioning trySingleton,
InitContainer, and the expected constraint so developers are aware.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.ts`:
- Around line 6-12: The removal logic can miss the original drag handle if the
selector changed because unbinding resolves the drag element from the current
selector instead of the one that was originally bound; update the ProModal
drag-drop registration to save the concrete elements (or at least the
dragElement reference) inside ProModal._dragDropListeners[containerSelector]
when adding, and then in ProModal.removeDragDrop use that stored dragElement and
stored listener references to remove the pointerdown handler and any visual CSS
class before deleting the entry; apply the same change to the other removal site
(the block around the second occurrence at the 52-61 region) so unbinding always
uses the originally-bound element instead of querying the selector again.

In `@src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModal.razor`:
- Around line 13-16: The markup reads component fields (AriaModal and
ShowOverlay) directly which bypasses overrides in ModalParameters after Merge;
update the template to use ModalParameters.AriaModal and
ModalParameters.ShowOverlay (and ensure GetRole() reads from ModalParameters or
add a GetRole(ModalParameters) overload) so aria-modal and role reflect
merged/overridden values from ModalParameters instead of the internal fields.

In `@src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalService.cs`:
- Line 135: The overlay click handler is always set to close the modal which
allows blocking modals to be dismissed; update the builder.AddComponentParameter
call that sets BitModal.OnOverlayClick (currently using
EventCallback.Factory.Create<MouseEventArgs>(modalReference, () =>
modalReference.Close())) to only wire that EventCallback when
modalParameters?.Blocking is false (or not true), and pass null/omit the
parameter when modalParameters?.Blocking is true so blocking modals cannot be
closed by clicking the overlay.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Surfaces/Modal/BitModalServiceDemo.razor`:
- Line 17: The example currently has the BitModalContainer commented out so
modals created via BitModalService will not render; either uncomment the
BitModalContainer line (restore the component invocation, e.g.,
BitModalContainer ModalParameters="@(new() { FullWidth = true })") so the demo
works, or add a brief explanatory note near the Show button/BitModalService
usage stating that consumers must include <BitModalContainer /> (or equivalent)
in their layout/root component to render modals; update references in this file
(BitModalContainer, BitModalService) accordingly so the demo is functional or
clearly documents the requirement.

---

Outside diff comments:
In `@src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Dialog/BitDialog.ts`:
- Around line 59-65: removeDragDrop currently exits early if
document.querySelector(dragElementSelector) returns null, leaking the entry in
Dialog._dragDropListeners and leaving document listeners attached; instead,
always read Dialog._dragDropListeners[id] and, even when dragElement is null,
remove the stored document-level listeners (using the listener functions from
the listeners object) and then delete Dialog._dragDropListeners[id]; only skip
element-specific cleanup when dragElement is null. Apply the same fix to the
other branch/duplicate logic around lines 70-80 that also assumes the element
exists.

---

Nitpick comments:
In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.razor.cs`:
- Around line 246-285: Add brief clarifying comments inside OnAfterRenderAsync
around the _offsetTop and AbsolutePosition logic: comment why _offsetTop = 0 is
set before calling ToggleScroll (it's a guard for the case when AutoToggleScroll
is false and ToggleScroll will not recalc _offsetTop), and comment why
StyleBuilder.Reset() and StateHasChanged() are invoked only when
AbsolutePosition is true (to ensure the computed top-offset style, which
ToggleScroll may have updated, is applied after render). Reference the symbols
_offsetTop, ToggleScroll, AutoToggleScroll, AbsolutePosition,
StyleBuilder.Reset, and OnAfterRenderAsync when placing the comments.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.scss`:
- Around line 37-110: Duplicate positioning classes cause redundant rules and
break RTL: replace the absolute-direction classes (.bit-mdl-tlf, .bit-mdl-trg,
.bit-mdl-clf, .bit-mdl-crg, .bit-mdl-blf, .bit-mdl-brg) so they use explicit
positioning (top/left/top/right/bottom/left/bottom/right) instead of flexbox,
and keep the logical-direction classes (.bit-mdl-tst, .bit-mdl-ten,
.bit-mdl-cst, .bit-mdl-cen, .bit-mdl-bst, .bit-mdl-ben) using flexbox
(align-items/justify-content) to preserve RTL behavior; remove the duplicated
flexbox rules from the absolute classes and consolidate identical rules to avoid
repetition.

In
`@src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModalClassStyles.cs`:
- Around line 3-34: The BitProModalClassStyles class lacks a Merge method so
derived properties (HeaderContainer, Header, CloseButton, CloseIcon, Body,
Footer) are not preserved when merging; add a static
Merge(BitProModalClassStyles? first, BitProModalClassStyles? second) that calls
BitModalClassStyles.Merge for base fields then merges each derived string
property (choose non-null/longer/concatenate as project convention) into the
result, ensuring the returned BitProModalClassStyles contains merged values for
Root/Overlay/Content plus HeaderContainer, Header, CloseButton, CloseIcon, Body,
and Footer.

In
`@src/BlazorUI/Bit.BlazorUI/Extensions/IBitBlazorUIServiceCollectionExtensions.cs`:
- Around line 10-17: The BitModalService singleton registration (controlled by
trySingleton) uses a single _container and when multiple BitModalContainer
instances call InitContainer the last caller wins; update documentation to state
that if you pass trySingleton=true you must ensure only one BitModalContainer
exists (or explain the last-InitContainer-wins behavior and that scoped
registration avoids it), and add this note to the public API surface — e.g., XML
docs on IBitBlazorUIServiceCollectionExtensions.AddBitBlazorUI/BitModalService
registration method and/or the BitModalService class mentioning trySingleton,
InitContainer, and the expected constraint so developers are aware.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 73cadd8e-9ee2-4813-a90b-6bf437b7a48f

📥 Commits

Reviewing files that changed from the base of the PR and between 842111d and 85e28a3.

📒 Files selected for processing (39)
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.razor
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.razor.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.scss
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.ts
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModalClassStyles.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModalJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Extensions/IBlazorUIExtrasServiceCollectionExtensions.cs
  • src/BlazorUI/Bit.BlazorUI.Extras/Styles/extra-components.scss
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Dialog/BitDialog.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Dialog/BitDialog.ts
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Dialog/BitDialogJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModal.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModal.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModal.scss
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalContainer.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalContainer.razor.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalParameters.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalReference.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalService.cs
  • src/BlazorUI/Bit.BlazorUI/Extensions/IBitBlazorUIServiceCollectionExtensions.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ModalService/BitModalServiceDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProModal/BitProModalDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProModal/BitProModalDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ProModal/BitProModalDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Surfaces/Modal/BitModalDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Surfaces/Modal/BitModalDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Surfaces/Modal/BitModalServiceDemo.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Surfaces/Modal/BitModalServiceDemo.razor.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Surfaces/Modal/BitModalServiceDemo.razor.scss
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Surfaces/Modal/ModalContent.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Home/ComponentsSection.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Shared/MainLayout.razor.NavItems.cs
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/compilerconfig.json
  • src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Extras/ProModal/BitProModalTests.cs
  • src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Surfaces/Modal/BitModalServiceTests.cs
  • src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Surfaces/Modal/BitModalTests.cs
  • src/BlazorUI/Tests/Bit.BlazorUI.Tests/Components/Surfaces/Modal/TestModalContent.cs
💤 Files with no reviewable changes (4)
  • src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Extras/ModalService/BitModalServiceDemo.razor
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalJsRuntimeExtensions.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalParameters.cs
  • src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModal.scss

Comment on lines +6 to +12
ProModal.removeDragDrop(containerSelector, dragElementSelector);
const listeners: any = {};
ProModal._dragDropListeners[containerSelector] = listeners;

const element = document.querySelector(containerSelector) as HTMLElement;
const dragElement = document.querySelector(dragElementSelector) as HTMLElement;
if (!element || !dragElement) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Cleanup can miss the originally bound drag handle when selector changes.

Listener state is keyed only by containerSelector, but unbinding resolves the drag element from the current selector argument. If the selector changes, the old element keeps pointerdown + visual state.

Suggested patch
 public static setupDragDrop(containerSelector: string, dragElementSelector: string) {
     ProModal.removeDragDrop(containerSelector, dragElementSelector);
     const listeners: any = {};
     ProModal._dragDropListeners[containerSelector] = listeners;

     const element = document.querySelector(containerSelector) as HTMLElement;
     const dragElement = document.querySelector(dragElementSelector) as HTMLElement;
     if (!element || !dragElement) return;
+    listeners['dragElement'] = dragElement;
@@
 public static removeDragDrop(containerSelector: string, dragElementSelector: string) {
     const listeners = ProModal._dragDropListeners[containerSelector];
     if (!listeners) return;

-    const dragElement = document.querySelector(dragElementSelector) as HTMLElement;
+    const dragElement = (listeners['dragElement'] as HTMLElement)
+        ?? (document.querySelector(dragElementSelector) as HTMLElement);
     if (dragElement) {
         dragElement.removeEventListener('pointerdown', listeners['pointerdown']);
         dragElement.style.cursor = '';
         dragElement.classList.remove('bit-mdl-nta');
     }
@@
     delete listeners['pointerup'];
+    delete listeners['dragElement'];
     delete ProModal._dragDropListeners[containerSelector];
 }

Also applies to: 52-61

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI.Extras/Components/ProModal/BitProModal.ts` around
lines 6 - 12, The removal logic can miss the original drag handle if the
selector changed because unbinding resolves the drag element from the current
selector instead of the one that was originally bound; update the ProModal
drag-drop registration to save the concrete elements (or at least the
dragElement reference) inside ProModal._dragDropListeners[containerSelector]
when adding, and then in ProModal.removeDragDrop use that stored dragElement and
stored listener references to remove the pointerdown handler and any visual CSS
class before deleting the entry; apply the same change to the other removal site
(the block around the second occurrence at the 52-61 region) so unbinding always
uses the originally-bound element instead of querying the selector again.

Comment on lines +13 to 16
aria-modal="@AriaModal.ToString().ToLowerInvariant()"
role="@GetRole()">
@if (ModalParameters.Modeless is false)
@if (ShowOverlay)
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Overlay and aria-modal rendering bypass merged modal parameters.

This reads component fields directly while the rest of the markup reads ModalParameters. That can drop container/service overrides after BitModalParameters.Merge(...).

Suggested patch
-         aria-modal="`@AriaModal.ToString`().ToLowerInvariant()"
+         aria-modal="`@ModalParameters.AriaModal.ToString`().ToLowerInvariant()"
@@
-        `@if` (ShowOverlay)
+        `@if` (ModalParameters.ShowOverlay)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
aria-modal="@AriaModal.ToString().ToLowerInvariant()"
role="@GetRole()">
@if (ModalParameters.Modeless is false)
@if (ShowOverlay)
{
aria-modal="`@ModalParameters.AriaModal.ToString`().ToLowerInvariant()"
role="`@GetRole`()">
`@if` (ModalParameters.ShowOverlay)
{
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModal.razor` around
lines 13 - 16, The markup reads component fields (AriaModal and ShowOverlay)
directly which bypasses overrides in ModalParameters after Merge; update the
template to use ModalParameters.AriaModal and ModalParameters.ShowOverlay (and
ensure GetRole() reads from ModalParameters or add a GetRole(ModalParameters)
overload) so aria-modal and role reflect merged/overridden values from
ModalParameters instead of the internal fields.

{
builder.AddComponentParameter(seq++, nameof(BitModal.OnOverlayClick), EventCallback.Factory.Create<MouseEventArgs>(modalReference, () => modalReference.Close()));
}
builder.AddComponentParameter(seq++, nameof(BitModal.OnOverlayClick), EventCallback.Factory.Create<MouseEventArgs>(modalReference, () => modalReference.Close()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Blocking behavior is bypassed for service-created modals.

Line 135 always wires overlay click to modalReference.Close(), so a blocking modal can still be dismissed by clicking outside. Please restore conditional wiring based on modalParameters?.Blocking.

Suggested fix
             builder.OpenComponent<BitModal>(seq++);
             builder.SetKey(modalReference.Id);
             builder.AddComponentParameter(seq++, nameof(BitModal.IsOpen), true);
-            builder.AddComponentParameter(seq++, nameof(BitModal.OnOverlayClick), EventCallback.Factory.Create<MouseEventArgs>(modalReference, () => modalReference.Close()));
+            if (modalParameters?.Blocking is not true)
+            {
+                builder.AddComponentParameter(seq++, nameof(BitModal.OnOverlayClick),
+                    EventCallback.Factory.Create<MouseEventArgs>(modalReference, () => modalReference.Close()));
+            }
             builder.AddComponentParameter(seq++, nameof(BitModal.ChildContent), content);
             builder.CloseComponent();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/BlazorUI/Bit.BlazorUI/Components/Surfaces/Modal/BitModalService.cs` at
line 135, The overlay click handler is always set to close the modal which
allows blocking modals to be dismissed; update the builder.AddComponentParameter
call that sets BitModal.OnOverlayClick (currently using
EventCallback.Factory.Create<MouseEventArgs>(modalReference, () =>
modalReference.Close())) to only wire that EventCallback when
modalParameters?.Blocking is false (or not true), and pass null/omit the
parameter when modalParameters?.Blocking is true so blocking modals cannot be
closed by clicking the overlay.

<div>Show a FullWidth modal using the BitModalService:</div><br/>
<BitButton OnClick="ShowModal">Show</BitButton>

@* <BitModalContainer ModalParameters="@(new() { FullWidth = true })" /> *@

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Example is non-functional without BitModalContainer.

The BitModalContainer is commented out (line 17), which means the modal shown by clicking the "Show" button will not render. The container is required to display modals created via BitModalService.

If this is intentional as a code snippet example, consider:

  1. Uncommenting the container to make the example functional, or
  2. Adding explanatory text that users must add <BitModalContainer /> to their layout
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Pages/Components/Surfaces/Modal/BitModalServiceDemo.razor`
at line 17, The example currently has the BitModalContainer commented out so
modals created via BitModalService will not render; either uncomment the
BitModalContainer line (restore the component invocation, e.g.,
BitModalContainer ModalParameters="@(new() { FullWidth = true })") so the demo
works, or add a brief explanatory note near the Show button/BitModalService
usage stating that consumers must include <BitModalContainer /> (or equivalent)
in their layout/root component to render modals; update references in this file
(BitModalContainer, BitModalService) accordingly so the demo is functional or
clearly documents the requirement.

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.

The BitProModal component is missing from the Extras project of the BlazorUI

1 participant