Skip to content

fix: Update dialog portal & banner layout improvements#112

Merged
AmintaCCCP merged 1 commit intomainfrom
fix/update-ui-improvements
Apr 26, 2026
Merged

fix: Update dialog portal & banner layout improvements#112
AmintaCCCP merged 1 commit intomainfrom
fix/update-ui-improvements

Conversation

@AmintaCCCP
Copy link
Copy Markdown
Owner

@AmintaCCCP AmintaCCCP commented Apr 26, 2026

Summary

Two fixes based on the screenshot report:

Issue 1: Update dialog mask trapped inside overflow container

UpdateChecker is rendered inside GeneralPanelSettingsPanel → scrollable content area. The fixed inset-0 overlay was constrained by the nearest overflow container, so the dark mask did not cover the full viewport.

Fix: Wrap the dialog with ReactDOM.createPortal to render at document.body level. Also added bg-black/50 shorthand, z-[9999] for reliable z-index stacking, and click-on-mask-to-dismiss behavior.

Issue 2: Banner button wrapping on narrow viewports

The "立即下载" button and close button in UpdateNotificationBanner could wrap to a new line when the viewport was narrow. Also had invalid/duplicate Tailwind classes (dark:bg-brand-indigo/20/20 with two opacity values, duplicate dark:bg-white/[0.04]).

Fix: Removed invalid duplicate classes, added gap-3, min-w-0, flex-shrink-0, flex-wrap, and line-clamp-1 to the banner flex layout so buttons stay on one line and text truncates gracefully.

Test plan

  • Open Settings → General, click "Check for Updates", observe new version dialog — the dark mask should cover the full window including the sidebar/header
  • Click the mask area outside the dialog — it should dismiss the dialog
  • Narrow the browser window to ~375px — the banner buttons should stay on one line

Summary by CodeRabbit

Style

  • Enhanced update notification styling with improved responsive design, better text handling, and refined spacing
  • Updated button styling with improved hover states across light and dark modes
  • Update dialog now closes when clicking outside the modal

- UpdateChecker: wrap the update dialog with ReactDOM.createPortal to
  render at document.body level, avoiding the fixed overlay being
  constrained by overflow containers in the Settings scroll area.
  Also use bg-black/50 syntax and z-[9999] for reliable stacking.
  Clicking the mask backdrop now dismisses the dialog.

- UpdateNotificationBanner: fix duplicate/invalid Tailwind classes
  (dark:bg-brand-indigo/20/20, duplicate dark:bg-white/[0.04] etc.);
  add gap-3, min-w-0, flex-shrink-0, flex-wrap, and line-clamp-1 to
  prevent the version/date row and the "Download" button from wrapping
  on narrow viewports.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

📝 Walkthrough

Walkthrough

The pull request refactors the update dialog to render via ReactDOM.createPortal into document.body with enhanced backdrop handling and dismissal logic, while updating the notification banner styling for improved responsiveness, spacing, and text overflow handling with adjusted button hover states.

Changes

Cohort / File(s) Summary
Modal Portal Migration
src/components/UpdateChecker.tsx
Relocates update dialog rendering from inline to ReactDOM.createPortal with z-[9999] stacking, adds backdrop onClick handler to dismiss on backdrop click (excluding internal modal clicks), and updates backdrop opacity to bg-black/50.
Notification Banner Styling
src/components/UpdateNotificationBanner.tsx
Adjusts Tailwind classes for responsive layout: removes dark-mode background, increases spacing with gap-3, adds min-w-0, flex-shrink-0, and flex-wrap for better text/icon wrapping, applies line-clamp-1 to changelog text, and updates button hover colors to hover:bg-brand-hover and dark:hover:bg-white/[0.08].

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Poem

🐰✨ A portal opens wide, the modal takes flight,
From nested depths to document's height,
With backdrop clicks and styling care,
The update dialog floats through the air! 🌙

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: Update dialog portal & banner layout improvements' directly reflects the two main changes: converting the dialog to use React portals and improving the banner's layout for responsive behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/update-ui-improvements

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
src/components/UpdateChecker.tsx (3)

110-111: Nit: redundant dark: modifiers.

dark:bg-brand-indigo/20 is identical to the base bg-brand-indigo/20, and similarly for dark:text-brand-violet on line 111 — these dark: overrides are no-ops and can be dropped to keep the class list clean. Same pattern recurs at line 33 of UpdateNotificationBanner.tsx.

♻️ Proposed change
-                  <div className="w-12 h-12 bg-brand-indigo/20 dark:bg-brand-indigo/20 rounded-full flex items-center justify-center">
-                    <Package className="w-6 h-6 text-brand-violet dark:text-brand-violet" />
+                  <div className="w-12 h-12 bg-brand-indigo/20 rounded-full flex items-center justify-center">
+                    <Package className="w-6 h-6 text-brand-violet" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/UpdateChecker.tsx` around lines 110 - 111, Remove the
redundant dark-mode Tailwind classes that duplicate the base styles: in
UpdateChecker.tsx remove "dark:bg-brand-indigo/20" from the div's className and
remove "dark:text-brand-violet" from the Package icon's className; also apply
the same cleanup in UpdateNotificationBanner.tsx where the identical "dark:"
overrides are used (line with the duplicate bg/text classes). This keeps the
class lists minimal while preserving styling.

2-2: Optional: prefer named createPortal import.

Other portal consumers in this repo (e.g., RepositoryCard.tsx, MarkdownRenderer.tsx) use import { createPortal } from 'react-dom'. Aligning here improves consistency and lets bundlers tree-shake the rest of react-dom.

♻️ Proposed change
-import ReactDOM from 'react-dom';
+import { createPortal } from 'react-dom';

And at the call site:

-        ReactDOM.createPortal(
+        createPortal(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/UpdateChecker.tsx` at line 2, Replace the default ReactDOM
import with a named createPortal import and update the portal call site: change
the import line to import { createPortal } from 'react-dom' and replace any
ReactDOM.createPortal(...) usage inside the UpdateChecker component (or helper
functions in this file) to just createPortal(...), keeping the same props and
children so behavior is unchanged; this aligns with other files like
RepositoryCard.tsx and allows tree-shaking.

100-167: Consider ESC handling, scroll lock, and basic a11y for the portal dialog.

Now that the modal escapes its scroll container and covers the full viewport, a few UX/a11y gaps become more noticeable:

  • No Escape key dismissal — users typically expect this for modals.
  • No body scroll lock — the page underneath the backdrop can still scroll while the dialog is open.
  • No role="dialog" / aria-modal="true" / aria-labelledby on the modal container, and no initial focus management.

These are pre-existing, but the portalization makes them worth addressing now.

♻️ Sketch of an effect to handle ESC + scroll lock
   const [error, setError] = useState<string | null>(null);
+
+  React.useEffect(() => {
+    if (!showUpdateDialog) return;
+    const onKey = (e: KeyboardEvent) => {
+      if (e.key === 'Escape') setShowUpdateDialog(false);
+    };
+    document.addEventListener('keydown', onKey);
+    const prevOverflow = document.body.style.overflow;
+    document.body.style.overflow = 'hidden';
+    return () => {
+      document.removeEventListener('keydown', onKey);
+      document.body.style.overflow = prevOverflow;
+    };
+  }, [showUpdateDialog]);

And on the inner container:

-            <div className="bg-white dark:bg-panel-dark rounded-xl shadow-xl max-w-md w-full max-h-[80vh] overflow-y-auto">
+            <div
+              role="dialog"
+              aria-modal="true"
+              aria-labelledby="update-dialog-title"
+              className="bg-white dark:bg-panel-dark rounded-xl shadow-xl max-w-md w-full max-h-[80vh] overflow-y-auto"
+            >

(and add id="update-dialog-title" to the h3).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/UpdateChecker.tsx` around lines 100 - 167, The modal created
when showUpdateDialog && updateInfo is true needs accessibility and UX
improvements: add an effect (e.g., inside UpdateChecker component) that on open
sets document.body.style.overflow = 'hidden' and restores it on close to lock
scrolling, attaches a keydown listener to call setShowUpdateDialog(false) when
Escape is pressed (cleanup on unmount), and manage initial focus by focusing the
modal container or the primary button (handleDownload) when opened; also add
role="dialog" aria-modal="true" and aria-labelledby referencing an id added to
the h3 (e.g., id="update-dialog-title") on the inner dialog div to provide
proper labelling. Ensure all listeners and body style changes are cleaned up
when showUpdateDialog becomes false or the component unmounts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/UpdateChecker.tsx`:
- Around line 110-111: Remove the redundant dark-mode Tailwind classes that
duplicate the base styles: in UpdateChecker.tsx remove "dark:bg-brand-indigo/20"
from the div's className and remove "dark:text-brand-violet" from the Package
icon's className; also apply the same cleanup in UpdateNotificationBanner.tsx
where the identical "dark:" overrides are used (line with the duplicate bg/text
classes). This keeps the class lists minimal while preserving styling.
- Line 2: Replace the default ReactDOM import with a named createPortal import
and update the portal call site: change the import line to import { createPortal
} from 'react-dom' and replace any ReactDOM.createPortal(...) usage inside the
UpdateChecker component (or helper functions in this file) to just
createPortal(...), keeping the same props and children so behavior is unchanged;
this aligns with other files like RepositoryCard.tsx and allows tree-shaking.
- Around line 100-167: The modal created when showUpdateDialog && updateInfo is
true needs accessibility and UX improvements: add an effect (e.g., inside
UpdateChecker component) that on open sets document.body.style.overflow =
'hidden' and restores it on close to lock scrolling, attaches a keydown listener
to call setShowUpdateDialog(false) when Escape is pressed (cleanup on unmount),
and manage initial focus by focusing the modal container or the primary button
(handleDownload) when opened; also add role="dialog" aria-modal="true" and
aria-labelledby referencing an id added to the h3 (e.g.,
id="update-dialog-title") on the inner dialog div to provide proper labelling.
Ensure all listeners and body style changes are cleaned up when showUpdateDialog
becomes false or the component unmounts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7925cc2e-654e-47b5-a8de-3cbc88e874f4

📥 Commits

Reviewing files that changed from the base of the PR and between 365c73a and eb47068.

📒 Files selected for processing (2)
  • src/components/UpdateChecker.tsx
  • src/components/UpdateNotificationBanner.tsx

@AmintaCCCP AmintaCCCP merged commit 53fb6b0 into main Apr 26, 2026
5 checks passed
@AmintaCCCP AmintaCCCP deleted the fix/update-ui-improvements branch April 26, 2026 09:36
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.

1 participant