Repository navigation
Conversation
Move the toast to a body portal so it can appear above stacking contexts, and use theme utility classes for its styling.
Contributor
There was a problem hiding this comment.
Code Review
This pull request refactors the Toast component to use React Portals, rendering it into the document body to avoid stacking context issues, and replaces inline styles with Tailwind CSS classes. The review feedback correctly points out a violation of the style guide regarding arbitrary Tailwind values and warns about potential server-side rendering (SSR) issues when accessing 'document.body' directly.
Comment on lines
+55
to
63
| return createPortal( | ||
| <div | ||
| role="status" | ||
| aria-live="polite" | ||
| data-non-print="true" | ||
| className="fixed left-1/2 bottom-24 z-50 flex items-center shadow-lg" | ||
| style={{ | ||
| transform: 'translateX(-50%)', | ||
| background: COLOR_SURFACE, | ||
| color: COLOR_TEXT, | ||
| borderRadius: 4, | ||
| padding: actionLabel ? '6px 8px 6px 16px' : '14px 16px', | ||
| gap: 8, | ||
| minHeight: 48, | ||
| maxWidth: 'min(560px, calc(100vw - 32px))', | ||
| fontFamily: FONT_STACK, | ||
| fontSize: 14, | ||
| lineHeight: '20px', | ||
| }} | ||
| className={`fixed top-3 right-6 z-50 flex items-center gap-2 min-h-12 max-w-[min(560px,calc(100vw-2rem))] rounded bg-theme-primary text-white text-body-medium shadow-lg ${ | ||
| actionLabel ? 'py-1.5 pl-4 pr-2' : 'py-3.5 px-4' | ||
| }`} | ||
| > |
Contributor
There was a problem hiding this comment.
[WARNING] Avoid arbitrary Tailwind classes and ensure SSR safety.
Explanation
- Arbitrary Tailwind Class: The class
max-w-[min(560px,calc(100vw-2rem))]on line 60 uses an arbitrary value, which violates the repository style guide for thenarratives/application (Section 11, line 211). We can achieve the same responsive layout using standard Tailwind classes likeleft-4 right-4 sm:left-auto sm:right-6 sm:max-w-xl. - SSR Safety: Accessing
document.bodydirectly on line 78 during server-side rendering (SSR) will throw aReferenceError: document is not definedbecausedocumentis not available on the server. Adding a guard clauseif (typeof window === 'undefined') return null;prevents this error.
References
- Repository Style Guide (Section 11 - Narratives): "Standard utilities only; avoid arbitrary values (e.g.
h-[3px])."
if (typeof window === 'undefined') {
return null;
}
return createPortal(
<div
role="status"
aria-live="polite"
data-non-print="true"
className={`fixed top-3 left-4 right-4 sm:left-auto sm:right-6 z-50 flex items-center gap-2 min-h-12 sm:max-w-xl rounded bg-theme-primary text-white text-body-medium shadow-lg ${
actionLabel ? 'py-1.5 pl-4 pr-2' : 'py-3.5 px-4'
}`}
>
References
- Standard utilities only; avoid arbitrary values (e.g. h-[3px]). (link)
Use inset spacing on narrow screens so toasts fit within the viewport, and restore right alignment with a wider maximum width at the `sm` breakpoint.
Keep the toast mounted through its exit animation, dismiss immediately when reduced motion is enabled, and add tests for timing. Move it to the bottom-right and use a theme-aware text color.
8 tasks done
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.
Move the toast to a body portal so it can appear above stacking contexts, animate its enter and exit, and use theme utility classes for its styling.
Overview
The PDF export toast was rendered inside the answer panel. Some of that panel's ancestors create their own stacking context, so the toast's
z-50only counted within that context and sticky layers (z-[5]) could cover it at some viewport sizes.The toast now renders through
createPortal(..., document.body), the same waytooltip.tsxalready does. This takes it out of the panel's stacking contexts, soz-50places it above the page. Tooltips (z-index: 60) still layer over it. It moves from bottom-center above the prompt box to bottom-right on desktop, and spans the width at the bottom on mobile.It now fades in sliding up and fades out sliding down, instead of appearing and disappearing abruptly. The component stays mounted until the exit animation has finished, then calls
onDismiss.While in the file, the inline
styleobjects and hard-coded colour/font constants were replaced with Tailwind theme utilities, pernarratives/FRONTEND.md.Related Issues
Fixes # (issue number)
Changes Made
narratives/ui/src/components/toast.tsxcreatePortalintodocument.body.fixed bottom-3 left-4 right-4 sm:left-auto sm:right-6 sm:max-w-xl z-50, with the text centred (wasleft-1/2 bottom-24with atranslateX(-50%)). Only standard utilities, no arbitrary values.animate-entertoken (300ms fade up) and leaves with a newanimate-exittoken (200ms, M3 standard accelerate). Both are gated withmotion-safe:. Dismissal is timer-based and checksuseReducedMotion()frommotion/react, so reduced-motion users dismiss immediately rather than waiting on ananimationendthat never fires.onDismissarrow, so the countdown reset on every render of the answer panel.actionLabel/onActionprops and their button. The only caller, the PDF export, never passed them.styleto utility classes:bg-theme-primary,text-on-theme-primary,text-body-medium,rounded,min-h-12, and padding equivalents. RemovedCOLOR_SURFACE,COLOR_TEXTandFONT_STACK.#322F35).data-non-print="true"is kept, so the print stylesheet still hides the portalled node.narratives/ui/src/index.css--color-on-theme-primarytoken:var(--theme-on-primary, #FFFFFF). Instances with a light primary can override it to keep 4.5:1 contrast.--animate-exittoken andexitkeyframes, the reverse ofenter.narratives/ui/src/components/toast.test.tsx(new)EXIT_MSmatches the--animate-exitduration inindex.css, following the existingHANDOFF_MScheck indata_agent.test.tsx.onDismissis called.onDismissfires without waiting for the exit.Testing Done
From
narratives/(Node v24.22.1 per.nvmrc):pnpm lint:ui:tsc --noEmitclean; Biome checked 59 files, no issues.pnpm test:ui: 33 files, 356 tests passed.pnpm build:ui: built successfully.Reviewer repro:
pnpm dev:ui, ask a question, wait for the answer to finish, and click Export PDF. "PDF downloaded" should slide up into the bottom-right (full width on mobile) above the page, stay about 5s, then slide down and disappear. With the OS "reduce motion" setting on, it should appear and disappear without animating.Risk & Rollback
None. This is a UI-only change to one component with a single caller (the PDF export confirmation). To roll back, revert the commit.
Follow-ups
None.
Checklist
AGENTS.mdand followedCODING_GUIDELINES.md, plusFRONTEND.mdfor UI changes.Note: Only Maintainers can approve and merge PRs. Expected initial review time: 3 business days.