feat(toast): add useToast hook with stacked notifications, mobile support, and docs#3493
feat(toast): add useToast hook with stacked notifications, mobile support, and docs#3493sergiocarracedo wants to merge 31 commits intofeat/dialogfrom
Conversation
- Add clearAll function to ToastProvider context and useToast hook - Add Clear All button to playground - Add entry animation for new stacked items (fade in from top) - Make expand animation speed dynamic based on item count - Fix bottom margin for stacked container
- minUncollapsedToasts -> minActiveToasts - collapsedItems -> stackedItems - collapsed variable -> stacked - variant 'collapsed' -> 'stacked' - Updated comments to use 'stacked' terminology
…no horizontal scroll
…rect promoting item
…className snap" This reverts commit cc5c935.
…osition toggle snap
… invisible front item
…cked layout animation snap
…d prevent promotion translate
…in AnimatePresence
📦 Alpha Package Version PublishedUse Use |
🔍 Visual review for your branch is published 🔍Here are the links to: |
|
|
||
| const addToast = useCallback((toast: ToastProviderItem) => { | ||
| setItems((prev) => [...prev, toast]) | ||
| }, []) |
There was a problem hiding this comment.
Duplicate toast IDs not replaced, just appended
High Severity
The addToast function always appends to the items array without checking for an existing toast with the same id. The documentation explicitly promises that "calling with the same id again replaces the existing toast," but the current implementation creates duplicates instead. This breaks the custom-ID deduplication feature and the documented API contract.
Additional Locations (1)
| const [remainingTime, setRemainingTime] = useState(duration || 0) | ||
| const [isPaused, setIsPaused] = useState(false) | ||
| const startTimeRef = useRef<number | null>(null) | ||
| const animationFrameRef = useRef<number | null>(null) |
There was a problem hiding this comment.
Unused refs leftover from previous implementation
Low Severity
startTimeRef and animationFrameRef appear to be leftovers from a previous implementation approach. startTimeRef is assigned a value but never read for any computation. animationFrameRef is checked and cleared but never assigned a value from requestAnimationFrame, so it's always null — making the cancelAnimationFrame call a no-op. Both can be removed to reduce confusion.
| */ | ||
| variant?: F0ToastVariant | ||
| /** | ||
|
|
There was a problem hiding this comment.
Stray JSDoc opening creates malformed comment block
Low Severity
An orphaned /** on line 18 (likely a leftover from a deleted property's JSDoc) merges with the actual /** on line 20 into a single malformed comment block. The resulting JSDoc for the actions property contains garbled content, which will show a messy tooltip in IDE IntelliSense. Removing the stray /** and blank line fixes it.


🚪 Why?
Problem
The app needed a toast notification system that could be used by developers through a simple hook API. The system needed to handle multiple simultaneous notifications gracefully (stacking/collapsing), work correctly on mobile viewports, and be well-documented so developers know how to use it.
https://www.figma.com/design/dzE3YAvPHj0bJ2FpHTBixp/%F0%9F%9A%A7-Toast?node-id=1-2831&p=f&t=yjV91SFE637oYGRs-0
Screen.Recording.2026-02-20.at.17.03.48.mov
🔑 What?
Changes
useToasthook — imperatively trigger toast notifications with variants (default,success,warning,error), configurable duration, actions (buttons/links), and programmatic control (removeToast,clearAll, custom ids)ToastProvider+ToastsContainer— renders toasts in a fixed bottom-right panel; manages two zones: an active area (oldest N toasts, fully interactive) and a stacked area (overflow toasts collapsed behind the active zone with scale/offset animation; expands on hover to show all)AnimatePresence+layoutfor smooth promotion of stacked toasts into the active area; suppresses spurious exit animations on promoted items via a ghost element strategy< 640pxall toasts go into a single non-expandable stack (no active area, no hover expansion); the front toast remains fully interactive; container width isw-full sm:w-[350px]useIsDesktop/useIsMobilehooks — thin wrappers arounduseMediaQuery('(min-width: 640px)')exported fromlib/exports.tsuseToast.mdx) — Storybook MDX page covering setup, basic usage, variants, actions, duration/persistence, programmatic control, an interactive demo, and full type referenceOrdering rules
itemsarray is oldest-first;active = items.slice(0, minActiveToasts)rendered viaflex-col-reverseso oldest sits at the bottomstacked = items.slice(minActiveToasts)— overflow toasts, oldest-first so index 0 is at front of stack (highest z-index)order✅ Verification
Tests
cycle-dependencies✔,format-react✔,lint-react(0 warnings, 0 errors) ✔Manual Verification
< 640px) all toasts collapse into a single stack with no hover expansionclearAll,removeToast, and customidwork as expectedNote
Medium Risk
Adds a new global notification system wired into
F0Providerand uses portal + animation/timer logic; regressions could affect app-wide rendering/perf and notification lifecycle, but changes are isolated to new code paths.Overview
Adds a new toast notification API to
packages/reactviauseToast/ToastProvider, including stacking/promotion animations, hover-to-expand behavior, and mobile-specific limits, with rendering done through a portal.Introduces an internal
F0Toastcomponent (variants, actions, progress/timer auto-dismiss) plus Storybook stories/MDX documentation, and wiresToastProviderintoF0Providerso the hook works by default. Also exports new viewport helpers (useIsDesktop/useIsMobile), updates Storybook to includesrc/internal, tweaks several story titles, and pins the Chromatic GitHub Action tov13.Written by Cursor Bugbot for commit 6bcd4bc. This will update automatically on new commits. Configure here.