-
-
Notifications
You must be signed in to change notification settings - Fork 2k
feat(toast): support multiple ToastProvider #5606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: canary
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: c6ab38f The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
@ChaserZ98 is attempting to deploy a commit to the HeroUI Inc Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughAdds support for multiple ToastProvider instances by introducing per-toaster queues keyed by Changes
Sequence Diagram(s)sequenceDiagram
participant UI as Caller (e.g., Button)
participant API as addToast / closeToast / closeAll
participant Map as globalToastQueues
participant TP as ToastProvider (toasterId)
UI->>API: addToast({ toasterId, ... })
API->>Map: get/create queue for toasterId
Map-->>API: ToastQueue
API-->>TP: enqueue toast in matching ToastQueue
TP->>TP: render region for its toasterId
UI->>API: closeToast({ key, toasterId })
API->>Map: get queue for toasterId
API-->>TP: close toast by key
UI->>API: closeAll(toasterId)
API->>Map: get queue for toasterId
API-->>TP: close all visible toasts
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate 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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
@heroui/accordion
@heroui/alert
@heroui/autocomplete
@heroui/avatar
@heroui/badge
@heroui/breadcrumbs
@heroui/button
@heroui/calendar
@heroui/card
@heroui/checkbox
@heroui/chip
@heroui/code
@heroui/date-input
@heroui/date-picker
@heroui/divider
@heroui/drawer
@heroui/dropdown
@heroui/form
@heroui/image
@heroui/input
@heroui/input-otp
@heroui/kbd
@heroui/link
@heroui/listbox
@heroui/menu
@heroui/modal
@heroui/navbar
@heroui/number-input
@heroui/pagination
@heroui/popover
@heroui/progress
@heroui/radio
@heroui/ripple
@heroui/scroll-shadow
@heroui/select
@heroui/skeleton
@heroui/slider
@heroui/snippet
@heroui/spacer
@heroui/spinner
@heroui/switch
@heroui/table
@heroui/tabs
@heroui/toast
@heroui/tooltip
@heroui/user
@heroui/react
@heroui/system
@heroui/system-rsc
@heroui/theme
@heroui/use-aria-accordion
@heroui/use-aria-accordion-item
@heroui/use-aria-button
@heroui/use-aria-link
@heroui/use-aria-modal-overlay
@heroui/use-aria-multiselect
@heroui/use-aria-overlay
@heroui/use-callback-ref
@heroui/use-clipboard
@heroui/use-data-scroll-overflow
@heroui/use-disclosure
@heroui/use-draggable
@heroui/use-form-reset
@heroui/use-image
@heroui/use-infinite-scroll
@heroui/use-intersection-observer
@heroui/use-is-mobile
@heroui/use-is-mounted
@heroui/use-measure
@heroui/use-pagination
@heroui/use-real-shape
@heroui/use-ref-state
@heroui/use-resize
@heroui/use-safe-layout-effect
@heroui/use-scroll-position
@heroui/use-ssr
@heroui/use-theme
@heroui/use-update-effect
@heroui/use-viewport-size
@heroui/aria-utils
@heroui/dom-animation
@heroui/framer-utils
@heroui/react-rsc-utils
@heroui/react-utils
@heroui/shared-icons
@heroui/shared-utils
@heroui/stories-utils
@heroui/test-utils
commit: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🔭 Outside diff range comments (1)
.changeset/great-keys-admire.md (1)
1-6: CloseToast API breaking change – adjust version or add compatibility overloadThe
closeToastsignature was updated fromcloseToast(key: string)tocloseToast({ key, toasterId }), which will break existing call sites and documentation. You must either:
Add a backward-compatible overload:
export function closeToast(key: string): void export function closeToast(opts: { key: string; toasterId?: string }): voidinternally delegating to the new signature, and keep this as a patch release.
Or bump this changeset to a minor (or major) version and clearly note the breaking change.
Also update documentation to cover:
- The default
toasterIdvalue ("heroui")- That
addToast,closeToast, andcloseAllaccept an optionaltoasterIdparameter- What happens when you target a non-existent
toasterIdAffected call sites:
- apps/docs/content/components/toast/close.raw.jsx:
closeToast(toastKey[toastKey.length – 1])- packages/components/toast/stories/toast.stories.tsx:
closeToast(toastKey[toastKey.length – 1])- apps/docs/content/blog/v2.8.0.mdx: update former
closeToast(key: string)description to reflect the new API and version bump
🧹 Nitpick comments (4)
packages/components/toast/src/use-toast.ts (1)
113-117: Docstring grammar + clarity nits for toasterIdMinor copy fix and clarity improvement.
Apply:
/** - * The id of the ToastProvider. The decides which ToastProvider will handle the toast. + * The id of the ToastProvider. Determines which ToastProvider will handle this toast. * @default "heroui" */ toasterId?: string;packages/components/toast/stories/toast.stories.tsx (2)
428-469: Nice MultiToaster demo; add cleanup to prevent cross-story leftoversWith per-id queues, the top-level decorator’s
closeAll()only clears the default “heroui” queue. MultiToaster uses “left” and “right”, so toasts can persist across story changes. Either:
- extend
closeAllto support closing all queues (see provider comment), then callcloseAll("all")in the decorator, or- add a local cleanup here to clear both queues on unmount.
Example local cleanup:
useEffect(() => { return () => { closeAll("left"); closeAll("right"); }; }, []);
575-580: Export of MultiToaster story: consider adding note in story descriptionTo aid discoverability, consider adding a short docs block explaining that
toasterIdroutes toasts to specific providers and that the default id is “heroui”.packages/components/toast/src/toast-provider.tsx (1)
87-97: closeAll should optionally support clearing all toasters; avoid using map for side-effectsTwo suggestions:
- Allow
closeAll("all")to clear every queue (handy for Storybook cleanup).- Use
forEachinstead ofmapfor side effects.-export const closeAll = (toasterId = defaultToasterId) => { - if (!globalToastQueues[toasterId]) { - return; - } - - const keys = globalToastQueues[toasterId].visibleToasts.map((toast) => toast.key); - - keys.map((key) => { - globalToastQueues[toasterId]?.close(key); - }); -}; +export const closeAll = (toasterId: string | "all" = defaultToasterId) => { + const closeFor = (id: string) => { + const queue = globalToastQueues[id]; + if (!queue) return; + queue.visibleToasts.forEach((t) => queue.close(t.key)); + }; + if (toasterId === "all") { + Object.keys(globalToastQueues).forEach(closeFor); + return; + } + closeFor(toasterId); +};If you adopt
"all", update Storybook decorators to callcloseAll("all").
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
.changeset/great-keys-admire.md(1 hunks)packages/components/toast/src/toast-provider.tsx(4 hunks)packages/components/toast/src/use-toast.ts(1 hunks)packages/components/toast/stories/toast.stories.tsx(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
packages/components/toast/stories/toast.stories.tsx (1)
packages/components/toast/src/toast-provider.tsx (2)
ToastProvider(36-64)addToast(66-72)
packages/components/toast/src/toast-provider.tsx (3)
packages/components/toast/src/use-toast.ts (1)
ToastProps(27-118)packages/components/toast/src/toast.tsx (1)
ToastProps(20-20)packages/components/toast/src/index.ts (5)
ToastProps(5-5)getToastQueue(9-9)addToast(9-9)closeToast(9-9)closeAll(9-9)
🔇 Additional comments (1)
packages/components/toast/stories/toast.stories.tsx (1)
428-469: Targeted toasts look good and demonstrate the API clearlyTwo providers with distinct
toasterIds and explicitaddToast({toasterId})calls make the intended behavior obvious. This is a solid example.
| export const addToast = ({toasterId = defaultToasterId, ...props}: ToastProps & ToastOptions) => { | ||
| if (!globalToastQueues[toasterId]) { | ||
| return null; | ||
| } | ||
|
|
||
| return globalToastQueue.add(props); | ||
| return globalToastQueues[toasterId].add(props); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Dropping toasts when queue doesn’t exist is surprising; lazily create instead
Returning null if the toasterId queue isn’t initialized causes lost toasts when addToast is called before the provider mounts. Prefer lazy creation for resilience.
-export const addToast = ({toasterId = defaultToasterId, ...props}: ToastProps & ToastOptions) => {
- if (!globalToastQueues[toasterId]) {
- return null;
- }
-
- return globalToastQueues[toasterId].add(props);
-};
+export const addToast = ({toasterId = defaultToasterId, ...props}: ToastProps & ToastOptions) => {
+ if (!globalToastQueues[toasterId]) {
+ getToastQueue(toasterId);
+ }
+ return globalToastQueues[toasterId].add(props);
+};If you intentionally want to drop toasts for non-existent providers, at least log a dev warning to aid debugging.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In packages/components/toast/src/toast-provider.tsx around lines 66 to 72, the
current addToast returns null when globalToastQueues[toasterId] is missing which
drops toasts; change it to lazily create the queue instead of returning null: if
the queue for toasterId doesn't exist, initialize it (using the same queue
creation/registration logic the provider uses) and then call its add method; if
you prefer to keep dropping behavior, at minimum emit a dev-only warning
indicating the toasterId is missing. Ensure the new initialization uses the
existing factory/registry so behavior matches mounted providers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the original logic of addToast. I doubt if I should switch to this. @wingkwong Can you take a look here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- please add tests
- please update docs
- please check coderabbitai's comments once
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (3)
packages/components/toast/src/toast-provider.tsx (3)
78-90: closeToast overloads: backward-compatible and solidAccepting both
closeToast(key: string)andcloseToast({key, toasterId?})maintains compatibility while enabling multi-toaster targeting. Guarding when the queue is missing is appropriate.
70-76: addToast currently drops toasts and changes return type; lazily create the queue to preserve APIReturning
nullwhen the queue is missing both drops toasts invoked before provider mount and changes the return type tostring | null(breaking for consumers expecting astring). Prefer lazy creation to preserve behavior and typings.Apply:
-export const addToast = ({toasterId = defaultToasterId, ...props}: ToastProps & ToastOptions) => { - if (!globalToastQueues[toasterId]) { - return null; - } - - return globalToastQueues[toasterId].add(props); -}; +export const addToast = ( + {toasterId = defaultToasterId, ...props}: ToastProps & ToastOptions +): string => { + if (!globalToastQueues[toasterId]) { + getToastQueue(toasterId); + } + return globalToastQueues[toasterId].add(props); +};If you intentionally want to drop toasts when a provider isn’t mounted, at least document the behavior and consider emitting a dev-only warning to aid debugging.
26-38: Restore string overload for backward compatibilityConfirmed that
getToastQueueis re-exported in
packages/components/toast/src/index.ts (line 9)
and there are no internal call sites using the string-only signature. To avoid breaking existing consumers, add back thestringoverload:Apply:
export function getToastQueue(): ToastQueue<ToastProps>; -export function getToastQueue(args: {toasterId: string}): ToastQueue<ToastProps>; +export function getToastQueue(toasterId: string): ToastQueue<ToastProps>; +export function getToastQueue(args: {toasterId: string}): ToastQueue<ToastProps>; export function getToastQueue(args?: string | {toasterId: string}) { - const toasterId = args?.toasterId || defaultToasterId; + const toasterId = + typeof args === "string" ? args : args?.toasterId || defaultToasterId; if (!globalToastQueues[toasterId]) { globalToastQueues[toasterId] = new ToastQueue({ maxVisibleToasts: Infinity, }); } return globalToastQueues[toasterId]; }
🧹 Nitpick comments (3)
packages/components/toast/src/toast-provider.tsx (3)
13-15: Good fix on queue typing; minor nit: prefer const over letSwitching to a non-nullable Record removes the nullable leak. Consider making the map binding immutable since you only mutate its properties.
- let globalToastQueues: Record<string, ToastQueue<ToastProps>> = {}; + const globalToastQueues: Record<string, ToastQueue<ToastProps>> = {};
40-50: Provider lifecycle cleanup to avoid stale queues (optional)If a
ToastProviderunmounts, its queue remains in the global registry indefinitely. Consider deleting the queue on unmount to prevent leaks and stale state.Within the component, right after obtaining
toastQueue, add:// Add import at top of file: // import {useEffect} from "react"; useEffect(() => { return () => { delete globalToastQueues[toasterId]; }; }, [toasterId]);Note: Ensure you don’t have multiple providers sharing the same
toasterId. If you do, you may need reference counting instead of unconditional delete.
92-102: Use forEach instead of map for side effects
mapis used only for side effects and creates an unused array. Switch toforEach(or a simplefor...of) for clarity and micro-optimization.- const keys = globalToastQueues[toasterId].visibleToasts.map((toast) => toast.key); - - keys.map((key) => { - globalToastQueues[toasterId]?.close(key); - }); + const keys = globalToastQueues[toasterId].visibleToasts.map((toast) => toast.key); + keys.forEach((key) => { + globalToastQueues[toasterId]?.close(key); + });
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
packages/components/toast/src/toast-provider.tsx(3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/components/toast/src/toast-provider.tsx (3)
packages/components/toast/src/use-toast.ts (1)
ToastProps(27-118)packages/components/toast/src/toast.tsx (1)
ToastProps(20-20)packages/components/toast/src/index.ts (6)
ToastProps(5-5)getToastQueue(9-9)ToastProvider(13-13)addToast(9-9)closeToast(9-9)closeAll(9-9)
🔇 Additional comments (2)
packages/components/toast/src/toast-provider.tsx (2)
23-23: Prop addition looks goodAdding
toasterId?: stringto the provider props cleanly exposes the multi-toaster capability with a sensible default handled elsewhere.
40-50: Confirm maxVisibleToasts semantics: queue is created with Infinity
getToastQueuealways constructs the queue withmaxVisibleToasts: Infinity, while the provider acceptsmaxVisibleToasts(default 3). IfToastQueue(from @react-stately/toast) enforces visibility at the queue level (typical), the Region-level prop may not cap visibility. Verify whether the Region also enforces the cap, or consider initializing/updating the queue with the provider’s value.If needed, one approach is to allow passing options into
getToastQueueupon first creation so the provider can set the cap for itstoasterId.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (4)
packages/components/toast/__tests__/toast.test.tsx (4)
221-238: Make assertions resilient to async rendering and DOM orderRelying on index-based regions from
screen.getAllByRole("region")can be brittle:
- Rendering of regions is conditional and may be async due to state updates/animation.
- DOM order could change in the future (e.g., portals, layout changes), making index-based checks flaky.
Prefer awaiting the regions, then selecting by
data-placement, and use text-based assertions for clarity.Apply this diff:
- await user.click(left_button); - await user.click(right_button); - const region = screen.getAllByRole("region"); + await user.click(left_button); + await user.click(right_button); + const regions = await screen.findAllByRole("region"); + expect(regions).toHaveLength(2); + const leftRegion = regions.find((r) => r.getAttribute("data-placement") === "bottom-left")!; + const rightRegion = regions.find((r) => r.getAttribute("data-placement") === "bottom-right")!; @@ - // check for left ToastProvider - expect(region[0]).toHaveAttribute("data-placement", "bottom-left"); - expect(region[0]).toContainHTML(left_title); - expect(region[0]).toContainHTML(left_description); - expect(region[0]).not.toContainHTML(right_title); - expect(region[0]).not.toContainHTML(right_description); + // check for left ToastProvider + expect(leftRegion).toHaveAttribute("data-placement", "bottom-left"); + expect(leftRegion).toHaveTextContent(left_title); + expect(leftRegion).toHaveTextContent(left_description); + expect(leftRegion).not.toHaveTextContent(right_title); + expect(leftRegion).not.toHaveTextContent(right_description); @@ - // check for right ToastProvider - expect(region[1]).toHaveAttribute("data-placement", "bottom-right"); - expect(region[1]).toContainHTML(right_title); - expect(region[1]).toContainHTML(right_description); - expect(region[1]).not.toContainHTML(left_title); - expect(region[1]).not.toContainHTML(left_description); + // check for right ToastProvider + expect(rightRegion).toHaveAttribute("data-placement", "bottom-right"); + expect(rightRegion).toHaveTextContent(right_title); + expect(rightRegion).toHaveTextContent(right_description); + expect(rightRegion).not.toHaveTextContent(left_title); + expect(rightRegion).not.toHaveTextContent(left_description);
179-186: Use camelCase for local identifiers in TS testsLocal constants in this test use snake_case, which is inconsistent with the rest of the file and common TS/JS conventions. Renaming improves readability and consistency.
Apply this diff:
- const left_toaster_id = "left"; - const left_title = "Left Toast Title"; - const left_description = "Left Toast Description"; + const leftToasterId = "left"; + const leftTitle = "Left Toast Title"; + const leftDescription = "Left Toast Description"; @@ - const right_toaster_id = "right"; - const right_title = "Right Toast Title"; - const right_description = "Right Toast Description"; + const rightToasterId = "right"; + const rightTitle = "Right Toast Title"; + const rightDescription = "Right Toast Description"; @@ - <ToastProvider placement="bottom-left" toasterId={left_toaster_id} /> - <ToastProvider placement="bottom-right" toasterId={right_toaster_id} /> + <ToastProvider placement="bottom-left" toasterId={leftToasterId} /> + <ToastProvider placement="bottom-right" toasterId={rightToasterId} /> @@ - addToast({ - title: left_title, - description: left_description, - toasterId: left_toaster_id, - }); + addToast({ + title: leftTitle, + description: leftDescription, + toasterId: leftToasterId, + }); @@ - addToast({ - title: right_title, - description: right_description, - toasterId: right_toaster_id, - }); + addToast({ + title: rightTitle, + description: rightDescription, + toasterId: rightToasterId, + }); @@ - const left_button = wrapper.getByTestId("left-button"); - const right_button = wrapper.getByTestId("right-button"); + const left_button = wrapper.getByTestId("left-button"); + const right_button = wrapper.getByTestId("right-button"); @@ - expect(region[0]).toContainHTML(left_title); - expect(region[0]).toContainHTML(left_description); - expect(region[0]).not.toContainHTML(right_title); - expect(region[0]).not.toContainHTML(right_description); + expect(region[0]).toContainHTML(leftTitle); + expect(region[0]).toContainHTML(leftDescription); + expect(region[0]).not.toContainHTML(rightTitle); + expect(region[0]).not.toContainHTML(rightDescription); @@ - expect(region[1]).toContainHTML(right_title); - expect(region[1]).toContainHTML(right_description); - expect(region[1]).not.toContainHTML(left_title); - expect(region[1]).not.toContainHTML(left_description); + expect(region[1]).toContainHTML(rightTitle); + expect(region[1]).toContainHTML(rightDescription); + expect(region[1]).not.toContainHTML(leftTitle); + expect(region[1]).not.toContainHTML(leftDescription);Also applies to: 189-199, 200-216, 218-222, 225-237
218-220: Preferscreenoverwrapperfor queriesTesting Library recommends using
screento avoid passing around render results and to align with user-centric queries. Minor consistency improvement.Apply this diff:
- const left_button = wrapper.getByTestId("left-button"); - const right_button = wrapper.getByTestId("right-button"); + const left_button = screen.getByTestId("left-button"); + const right_button = screen.getByTestId("right-button");
178-238: Consider adding coverage for close APIs scoped by toasterIdOptional but valuable: add tests exercising
closeToast/closeAllwith atoasterIdto ensure they only affect the targeted provider’s queue. This would lock in the per-toaster isolation for both add and removal paths.If helpful, I can draft a companion test that renders two providers, creates one toast in each, calls
closeAll({toasterId: "left"}), and asserts only the left toast is removed.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
packages/components/toast/__tests__/toast.test.tsx(1 hunks)packages/components/toast/stories/toast.stories.tsx(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/components/toast/stories/toast.stories.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/components/toast/__tests__/toast.test.tsx (2)
packages/components/toast/src/toast-provider.tsx (2)
ToastProvider(40-68)addToast(70-76)packages/components/toast/src/index.ts (2)
ToastProvider(13-13)addToast(9-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Prettier
- GitHub Check: Continuous Release
- GitHub Check: ESLint
- GitHub Check: TypeScript
- GitHub Check: Tests
- GitHub Check: Build
🔇 Additional comments (1)
packages/components/toast/__tests__/toast.test.tsx (1)
178-216: Multi-provider isolation test is solidGood end-to-end coverage proving that distinct ToastProvider instances render only their own toasts when addressed via toasterId. This guards against the regression that originally duplicated toasts across providers.
… left and right region
…he effect in small screen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
apps/docs/content/components/toast/index.ts (1)
9-9: Nit: consider normalizing filename casing to kebab-caseOther entries here use lowercase kebab-case (e.g.,
custom-styles,custom-close-icon). For consistency, consider renaming the files tomultiple-toast-provider.*and updating this import accordingly.Apply this change if you proceed with the rename:
-import multipleToastProvider from "./multiple-ToastProvider"; +import multipleToastProvider from "./multiple-toast-provider";apps/docs/content/components/toast/multiple-ToastProvider.raw.jsx (1)
10-13: Nit: wrapperfixedcontainer may be redundantIf
ToastProviderrenders via a portal and already handles its own positioning/z-index, the outer<div className="fixed z-[100]">is unnecessary. Consider removing it to avoid creating an extra stacking context.Suggested edit:
- <div className="fixed z-[100]"> - <ToastProvider placement="top-right" toastOffset={60} toasterId={topToasterId} /> - <ToastProvider placement="bottom-right" toasterId={bottomToasterId} /> - </div> + <ToastProvider placement="top-right" toastOffset={60} toasterId={topToasterId} /> + <ToastProvider placement="bottom-right" toasterId={bottomToasterId} />If
ToastProviderdoes not portal, keep the wrapper or movefixed/z-index handling into the provider(s) for consistency.apps/docs/content/components/toast/multiple-ToastProvider.ts (1)
1-1: Nit: align import path casing if you rename the filesIf you adopt kebab-case for the demo filenames, update this import accordingly:
-import App from "./multiple-ToastProvider.raw.jsx?raw"; +import App from "./multiple-toast-provider.raw.jsx?raw";
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
apps/docs/content/components/toast/index.ts(2 hunks)apps/docs/content/components/toast/multiple-ToastProvider.raw.jsx(1 hunks)apps/docs/content/components/toast/multiple-ToastProvider.ts(1 hunks)apps/docs/content/docs/components/toast.mdx(1 hunks)packages/components/toast/stories/toast.stories.tsx(2 hunks)
✅ Files skipped from review due to trivial changes (1)
- apps/docs/content/docs/components/toast.mdx
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/components/toast/stories/toast.stories.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/docs/content/components/toast/multiple-ToastProvider.ts (1)
apps/docs/content/components/toast/multiple-ToastProvider.raw.jsx (1)
App(4-42)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: TypeScript
- GitHub Check: Continuous Release
- GitHub Check: Build
- GitHub Check: Tests
🔇 Additional comments (5)
apps/docs/content/components/toast/index.ts (2)
9-9: Docs wiring for multi-toaster example looks goodThe new import is correct and matches the filename in this folder.
20-21: Export surface updated correctlyIncluding
multipleToastProviderintoastContentmakes the demo available to the docs. Looks good.apps/docs/content/components/toast/multiple-ToastProvider.raw.jsx (2)
1-2: Imports and API usage are aligned with multi-toaster designUsing
ToastProvidertwice with distincttoasterIds and routingaddToastcalls via the same IDs demonstrates the feature cleanly.
18-23: Targeted toasts viatoasterIdare correctBoth handlers correctly route toasts to their intended providers using the matching
toasterId. Clear and minimal example.Also applies to: 29-35
apps/docs/content/components/toast/multiple-ToastProvider.ts (1)
1-9: Re-export pattern for raw demo content is consistentThe mapping to expose “/App.jsx” for the CodeDemo/Sandpack setup matches existing docs patterns. Looks good.
wingkwong
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please resolve the conflicts
Related Issue #5559
Related PR #5602 #5590
📝 Description
This PR adopts the multi toaster design from
react-hot-toastand adds support for multipleToastProvider. It might provide an alternative solution to the above issues and PRs.⛳️ Current behavior (updates)
All the
ToastProviderinstance shares the sameToastQueue. If there are multipleToastProviderinstances, calladdToastonce would cause all theToastProviderinstance to render the same toast, and hence create duplicate toasts.🚀 New behavior
Each
ToastProviderinstance has their ownToastQueueand an unique ID fieldtoasterId(default toheroui) is used to distinguish each of them.addToastandcloseToastAPI also receives thistoasterIdfield to control the add and close behavior of the correspondingToastProvider.💣 Is this a breaking change (Yes/No):
Only a bit change on params, so kind of a no.
📝 Additional Information
Please see the storybook
Multi Toasterexample for demo.Summary by CodeRabbit
New Features
Documentation
Tests
Chores