Skip to content

Conversation

@ariansj01
Copy link

@ariansj01 ariansj01 commented Oct 12, 2025

Screenshot 2025-10-12 121624 # Fix: Toast disableAnimation Not Respecting Immediate Close

from the #5802

Description

When disableAnimation={true} was set on ToastProvider, toasts still had a 300ms delay before closing when closeToast() or closeAll() was called programmatically. This issue occurred because the closeToast function always applied a 300ms timeout regardless of the disableAnimation setting.

Root Cause

The closeToast function in toast-provider.tsx had a hardcoded 300ms timeout that was always applied:

// Previous problematic code
export const closeToast = (key: string) => {
  const timeoutId = setTimeout(() => {
    globalToastQueue?.close(key);
  }, 300); // ← Always had delay
}

Solution

1. Added Global State Tracking

let globalDisableAnimation: boolean = false;

2. Store Settings in ToastProvider

export const ToastProvider = ({ disableAnimation: disableAnimationProp = false, ... }) => {
  const disableAnimation = disableAnimationProp ?? globalContext?.disableAnimation ?? false;
  globalDisableAnimation = disableAnimation; // Store globally
}

3. Enhanced closeToast Function

export const closeToast = (key: string, disableAnimation?: boolean) => {
  const shouldDisableAnimation = disableAnimation ?? globalDisableAnimation;
  
  if (shouldDisableAnimation) {
    globalToastQueue?.close(key); // Close immediately
    return;
  }
  
  // Otherwise, use 300ms delay
  setTimeout(() => {
    globalToastQueue?.close(key);
  }, 300);
}

Result

Now when disableAnimation={true} is set:

<ToastProvider disableAnimation={true}>
  <App />
</ToastProvider>

And using closeToast():

closeToast(toastKey); // Closes immediately ✅
closeAll(); // All toasts close immediately ✅

Testing

  • ✅ All existing tests pass (8/8 tests)
  • ✅ Backward compatibility maintained
  • ✅ Works correctly with both disableAnimation={true} and disableAnimation={false}

Files Changed

  • packages/components/toast/src/toast-provider.tsx

TypeScript Changes

  • Added optional disableAnimation parameter to closeToast and closeAll functions
  • Added global state tracking for disableAnimation setting
  • Enhanced function logic to respect the global disableAnimation setting

Fixes the issue where toasts with disableAnimation={true} still had a 300ms delay before closing when using programmatic close methods.90

Summary by CodeRabbit

  • New Features

    • Added option to disable toast animations globally or per action, allowing immediate single or bulk closures.
  • Bug Fixes

    • Date Range Picker now updates the correct start or end value based on focus, preventing incorrect ranges during navigation.
  • Documentation

    • Added a Troubleshooting section for Date Range Picker with common pitfalls and code samples.
    • Clarified API notes for handling focus changes across start and end dates.

@changeset-bot
Copy link

changeset-bot bot commented Oct 12, 2025

⚠️ No Changeset found

Latest commit: e126a87

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Oct 12, 2025

@ariansj01 is attempting to deploy a commit to the HeroUI Inc Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 12, 2025

Walkthrough

Updates DateRangePicker example logic to handle both start/end focus changes, expands DateRangePicker docs with troubleshooting and clarified onFocusChange description, and modifies toast provider to support immediate close via a disableAnimation flag (including a global flag), updating closeToast and closeAll signatures and control flow.

Changes

Cohort / File(s) Summary
DateRangePicker example logic
apps/docs/content/components/date-range-picker/presets.raw.jsx
Adjusts onFocusChange handler to conditionally update start or end value based on incoming focus ("start" or "end").
DateRangePicker documentation
apps/docs/content/docs/components/date-range-picker.mdx
Adds Troubleshooting section and clarifies API docs for onFocusChange handling; includes guidance and code snippets.
Toast provider immediate close
packages/components/toast/src/toast-provider.tsx
Introduces globalDisableAnimation; updates closeToast(key, disableAnimation?) and closeAll(disableAnimation?) to allow immediate closure when disableAnimation is true, otherwise preserves delayed (300ms) behavior.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant App
  participant ToastProvider
  participant ToastItem

  User->>App: Trigger closeAll(disableAnimation?)
  App->>ToastProvider: closeAll(disableAnimation?)
  alt disableAnimation true OR globalDisableAnimation
    loop For each visible toast
      ToastProvider->>ToastItem: close(immediate)
      ToastItem-->>ToastProvider: removed instantly
    end
  else Delayed close path
    loop For each visible toast
      ToastProvider->>ToastItem: close(start exit)
      Note over ToastItem: 300ms delay/animation
      ToastItem-->>ToastProvider: removed after delay
    end
  end
Loading
sequenceDiagram
  autonumber
  participant User
  participant DateRangePicker
  participant ExampleState as Example State

  User->>DateRangePicker: Focus changes to "start" or "end"
  DateRangePicker->>ExampleState: onFocusChange(val)
  alt val == "start"
    ExampleState->>ExampleState: setRange({ start: newStart, ... })
  else val == "end"
    ExampleState->>ExampleState: setRange({ end: newEnd, ... })
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

Suggested labels

👀 Status: To Review

Suggested reviewers

  • jrgarciadev
  • wingkwong
  • macci001

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description does not follow the repository’s required template as it omits the “Closes #” line, does not use the prescribed headings for current and new behavior, and lacks sections for breaking change status and additional information. Please update the description to include the “Closes #” line, use the exact template headings for current behavior, new behavior, breaking change status, and additional information, ensuring each section is clearly labeled as shown in the repository template.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly summarizes the main fix by referencing the disableAnimation and immediate close behavior for the Toast component, matching the primary change; it is concise and directly related to the patch.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
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.

Actionable comments posted: 3

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3737870 and e126a87.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • apps/docs/content/components/date-range-picker/presets.raw.jsx (1 hunks)
  • apps/docs/content/docs/components/date-range-picker.mdx (2 hunks)
  • packages/components/toast/src/toast-provider.tsx (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/components/toast/src/toast-provider.tsx (1)
packages/components/toast/src/index.ts (2)
  • closeToast (9-9)
  • closeAll (9-9)
🔇 Additional comments (3)
packages/components/toast/src/toast-provider.tsx (3)

87-94: LGTM! Immediate closure logic is correct.

The conditional immediate closure path properly bypasses the timeout when disableAnimation is true, fixing the reported issue. The fallback to globalDisableAnimation ensures programmatic calls respect the provider's setting.


104-113: LGTM! closeAll correctly propagates disableAnimation.

The disableAnimation parameter is properly passed through to each closeToast call, enabling batch immediate closure when requested.


14-14: Ensure single ToastProvider or refactor disableAnimation to React context.
Global globalDisableAnimation is mutated on each render, assuming a singleton provider; multiple instances will override the flag and cause unpredictable animations.

Comment on lines +99 to +105
onFocusChange: (val) => {
if (val === "start") {
setValue({...value, start: val});
} else if (val === "end") {
setValue({...value, end: val});
}
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical logic error: onFocusChange incorrectly assigns string to date value.

The code checks if val === "start" or val === "end", but then directly assigns val to the date fields. This will set value.start or value.end to the string "start" or "end" instead of a valid date value, breaking the DateRangePicker.

Based on React Aria's RangeCalendar API, onFocusChange receives a DateValue (the newly focused date), not a focus indicator string. The conditional checks for string equality are incorrect.

Apply this diff to fix the logic:

-        onFocusChange: (val) => {
-          if (val === "start") {
-            setValue({...value, start: val});
-          } else if (val === "end") {
-            setValue({...value, end: val});
-          }
-        },
+        onFocusChange: (val) => {
+          setValue({...value, start: val});
+        },

If you need to track which field (start or end) is focused, use a separate state variable or the calendar's built-in focus management rather than inspecting the date value.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/docs/content/components/date-range-picker/presets.raw.jsx around lines
99–105, onFocusChange currently treats its argument as a focus indicator string
and assigns "start"/"end" into the date fields, which is wrong; replace this
logic so onFocusChange treats val as a DateValue (or null) and does not assign
literal strings into value.start/value.end — either update a separate
focus-tracking state (e.g., focusedField = "start" | "end") and use that to
decide which date field to update elsewhere, or if the intent was only to track
focus, set a focusedDate/focusedField state instead of mutating the date range;
remove the string-equality checks and only assign DateValue objects to date
fields (or avoid assigning here entirely and update dates via the proper
onChange handler).

Comment on lines +369 to +384
#### onFocusChange only updates start date
When using `onFocusChange` in DateRangePicker, make sure to handle both start and end date focus changes properly:

```jsx
// ❌ Wrong - only updates start date
onFocusChange: (val) => setValue({...value, start: val}),

// ✅ Correct - handles both start and end focus
onFocusChange: (val) => {
if (val === "start") {
setValue({...value, start: val});
} else if (val === "end") {
setValue({...value, end: val});
}
},
```
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Documentation example contains the same logic error as the implementation.

The "correct" example shows the same bug present in presets.raw.jsx: checking if val === "start" or val === "end", then assigning val directly to the date fields. This treats val as both a focus indicator (string) and a date value, which is incorrect.

Based on React Aria's RangeCalendar API, onFocusChange receives a DateValue (the newly focused date cell), not a string indicator. The example will cause value.start or value.end to be set to the literal strings "start" or "end".

Update the documentation to show the correct pattern:

-#### onFocusChange only updates start date
-When using `onFocusChange` in DateRangePicker, make sure to handle both start and end date focus changes properly:
-
-```jsx
-// ❌ Wrong - only updates start date
-onFocusChange: (val) => setValue({...value, start: val}),
-
-// ✅ Correct - handles both start and end focus
-onFocusChange: (val) => {
-  if (val === "start") {
-    setValue({...value, start: val});
-  } else if (val === "end") {
-    setValue({...value, end: val});
-  }
-},
-```
+#### onFocusChange receives DateValue, not a focus indicator
+When using `onFocusChange` in RangeCalendar, the callback receives the newly focused `DateValue`:
+
+```jsx
+// ✅ Correct - updates the focused value
+onFocusChange: (val) => {
+  setValue({...value, start: val});
+},
+```
+
+If you need to track whether the start or end field is focused, use `calendarProps.focusedValue` to determine which field should be updated, or manage focus state separately.
🤖 Prompt for AI Agents
In apps/docs/content/docs/components/date-range-picker.mdx around lines 369 to
384, the docs incorrectly treat the onFocusChange callback value as a focus
indicator string and assign it to start/end; instead update the text and example
to state that onFocusChange receives a DateValue (the newly focused date cell)
and show a correct pattern that sets the appropriate date field (e.g.,
setValue({...value, start: val}) for the focused start), and add guidance to use
calendarProps.focusedValue or separate focus state if you need to know whether
start or end is focused; replace the broken examples with this corrected
explanation and example.

attribute: "onFocusChange",
type: "(isFocused: boolean) => void",
description: "Handler that is called when the element's focus status changes.",
description: "Handler that is called when the element's focus status changes. **Important**: When using this with DateRangePicker, make sure to handle both start and end date focus changes properly to avoid losing the end date selection.",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent with the flawed troubleshooting example.

The warning about handling both start and end date focus changes aligns with the incorrect troubleshooting example above (lines 377-384), which contains the same logic error as the implementation.

Update this description to accurately reflect the correct onFocusChange behavior:

-      description: "Handler that is called when the element's focus status changes. **Important**: When using this with DateRangePicker, make sure to handle both start and end date focus changes properly to avoid losing the end date selection.",
+      description: "Handler that is called when the element's focus status changes. The callback receives a `DateValue` representing the newly focused date in the calendar.",
📝 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
description: "Handler that is called when the element's focus status changes. **Important**: When using this with DateRangePicker, make sure to handle both start and end date focus changes properly to avoid losing the end date selection.",
description: "Handler that is called when the element's focus status changes. The callback receives a `DateValue` representing the newly focused date in the calendar.",
🤖 Prompt for AI Agents
In apps/docs/content/docs/components/date-range-picker.mdx around line 729, the
description for onFocusChange repeats the flawed troubleshooting logic and
misstates behavior; update the text to clearly describe the actual onFocusChange
contract: it receives the new focused input value (e.g., 'startDate', 'endDate'
or null), callers should update their focusedInput state to that value (not
toggle or infer the other field), and ensure focus changes do not inadvertently
clear or overwrite the other date value — replace the warning with concise
guidance on handling the focusedInput value directly and preserving start/end
date values.

@vercel
Copy link

vercel bot commented Oct 12, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
heroui Ready Ready Preview Comment Oct 12, 2025 11:04am
heroui-sb Ready Ready Preview Comment Oct 12, 2025 11:04am

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 12, 2025

Open in StackBlitz

@heroui/accordion

npm i https://pkg.pr.new/@heroui/accordion@5804

@heroui/alert

npm i https://pkg.pr.new/@heroui/alert@5804

@heroui/autocomplete

npm i https://pkg.pr.new/@heroui/autocomplete@5804

@heroui/avatar

npm i https://pkg.pr.new/@heroui/avatar@5804

@heroui/badge

npm i https://pkg.pr.new/@heroui/badge@5804

@heroui/breadcrumbs

npm i https://pkg.pr.new/@heroui/breadcrumbs@5804

@heroui/button

npm i https://pkg.pr.new/@heroui/button@5804

@heroui/calendar

npm i https://pkg.pr.new/@heroui/calendar@5804

@heroui/card

npm i https://pkg.pr.new/@heroui/card@5804

@heroui/checkbox

npm i https://pkg.pr.new/@heroui/checkbox@5804

@heroui/chip

npm i https://pkg.pr.new/@heroui/chip@5804

@heroui/code

npm i https://pkg.pr.new/@heroui/code@5804

@heroui/date-input

npm i https://pkg.pr.new/@heroui/date-input@5804

@heroui/date-picker

npm i https://pkg.pr.new/@heroui/date-picker@5804

@heroui/divider

npm i https://pkg.pr.new/@heroui/divider@5804

@heroui/drawer

npm i https://pkg.pr.new/@heroui/drawer@5804

@heroui/dropdown

npm i https://pkg.pr.new/@heroui/dropdown@5804

@heroui/form

npm i https://pkg.pr.new/@heroui/form@5804

@heroui/image

npm i https://pkg.pr.new/@heroui/image@5804

@heroui/input

npm i https://pkg.pr.new/@heroui/input@5804

@heroui/input-otp

npm i https://pkg.pr.new/@heroui/input-otp@5804

@heroui/kbd

npm i https://pkg.pr.new/@heroui/kbd@5804

@heroui/link

npm i https://pkg.pr.new/@heroui/link@5804

@heroui/listbox

npm i https://pkg.pr.new/@heroui/listbox@5804

@heroui/menu

npm i https://pkg.pr.new/@heroui/menu@5804

@heroui/modal

npm i https://pkg.pr.new/@heroui/modal@5804

@heroui/navbar

npm i https://pkg.pr.new/@heroui/navbar@5804

@heroui/number-input

npm i https://pkg.pr.new/@heroui/number-input@5804

@heroui/pagination

npm i https://pkg.pr.new/@heroui/pagination@5804

@heroui/popover

npm i https://pkg.pr.new/@heroui/popover@5804

@heroui/progress

npm i https://pkg.pr.new/@heroui/progress@5804

@heroui/radio

npm i https://pkg.pr.new/@heroui/radio@5804

@heroui/ripple

npm i https://pkg.pr.new/@heroui/ripple@5804

@heroui/scroll-shadow

npm i https://pkg.pr.new/@heroui/scroll-shadow@5804

@heroui/select

npm i https://pkg.pr.new/@heroui/select@5804

@heroui/skeleton

npm i https://pkg.pr.new/@heroui/skeleton@5804

@heroui/slider

npm i https://pkg.pr.new/@heroui/slider@5804

@heroui/snippet

npm i https://pkg.pr.new/@heroui/snippet@5804

@heroui/spacer

npm i https://pkg.pr.new/@heroui/spacer@5804

@heroui/spinner

npm i https://pkg.pr.new/@heroui/spinner@5804

@heroui/switch

npm i https://pkg.pr.new/@heroui/switch@5804

@heroui/table

npm i https://pkg.pr.new/@heroui/table@5804

@heroui/tabs

npm i https://pkg.pr.new/@heroui/tabs@5804

@heroui/toast

npm i https://pkg.pr.new/@heroui/toast@5804

@heroui/tooltip

npm i https://pkg.pr.new/@heroui/tooltip@5804

@heroui/user

npm i https://pkg.pr.new/@heroui/user@5804

@heroui/react

npm i https://pkg.pr.new/@heroui/react@5804

@heroui/system

npm i https://pkg.pr.new/@heroui/system@5804

@heroui/system-rsc

npm i https://pkg.pr.new/@heroui/system-rsc@5804

@heroui/theme

npm i https://pkg.pr.new/@heroui/theme@5804

@heroui/use-aria-accordion

npm i https://pkg.pr.new/@heroui/use-aria-accordion@5804

@heroui/use-aria-accordion-item

npm i https://pkg.pr.new/@heroui/use-aria-accordion-item@5804

@heroui/use-aria-button

npm i https://pkg.pr.new/@heroui/use-aria-button@5804

@heroui/use-aria-link

npm i https://pkg.pr.new/@heroui/use-aria-link@5804

@heroui/use-aria-modal-overlay

npm i https://pkg.pr.new/@heroui/use-aria-modal-overlay@5804

@heroui/use-aria-multiselect

npm i https://pkg.pr.new/@heroui/use-aria-multiselect@5804

@heroui/use-aria-overlay

npm i https://pkg.pr.new/@heroui/use-aria-overlay@5804

@heroui/use-callback-ref

npm i https://pkg.pr.new/@heroui/use-callback-ref@5804

@heroui/use-clipboard

npm i https://pkg.pr.new/@heroui/use-clipboard@5804

@heroui/use-data-scroll-overflow

npm i https://pkg.pr.new/@heroui/use-data-scroll-overflow@5804

@heroui/use-disclosure

npm i https://pkg.pr.new/@heroui/use-disclosure@5804

@heroui/use-draggable

npm i https://pkg.pr.new/@heroui/use-draggable@5804

@heroui/use-form-reset

npm i https://pkg.pr.new/@heroui/use-form-reset@5804

@heroui/use-image

npm i https://pkg.pr.new/@heroui/use-image@5804

@heroui/use-infinite-scroll

npm i https://pkg.pr.new/@heroui/use-infinite-scroll@5804

@heroui/use-intersection-observer

npm i https://pkg.pr.new/@heroui/use-intersection-observer@5804

@heroui/use-is-mobile

npm i https://pkg.pr.new/@heroui/use-is-mobile@5804

@heroui/use-is-mounted

npm i https://pkg.pr.new/@heroui/use-is-mounted@5804

@heroui/use-measure

npm i https://pkg.pr.new/@heroui/use-measure@5804

@heroui/use-pagination

npm i https://pkg.pr.new/@heroui/use-pagination@5804

@heroui/use-real-shape

npm i https://pkg.pr.new/@heroui/use-real-shape@5804

@heroui/use-ref-state

npm i https://pkg.pr.new/@heroui/use-ref-state@5804

@heroui/use-resize

npm i https://pkg.pr.new/@heroui/use-resize@5804

@heroui/use-safe-layout-effect

npm i https://pkg.pr.new/@heroui/use-safe-layout-effect@5804

@heroui/use-scroll-position

npm i https://pkg.pr.new/@heroui/use-scroll-position@5804

@heroui/use-ssr

npm i https://pkg.pr.new/@heroui/use-ssr@5804

@heroui/use-theme

npm i https://pkg.pr.new/@heroui/use-theme@5804

@heroui/use-update-effect

npm i https://pkg.pr.new/@heroui/use-update-effect@5804

@heroui/use-viewport-size

npm i https://pkg.pr.new/@heroui/use-viewport-size@5804

@heroui/aria-utils

npm i https://pkg.pr.new/@heroui/aria-utils@5804

@heroui/dom-animation

npm i https://pkg.pr.new/@heroui/dom-animation@5804

@heroui/framer-utils

npm i https://pkg.pr.new/@heroui/framer-utils@5804

@heroui/react-rsc-utils

npm i https://pkg.pr.new/@heroui/react-rsc-utils@5804

@heroui/react-utils

npm i https://pkg.pr.new/@heroui/react-utils@5804

@heroui/shared-icons

npm i https://pkg.pr.new/@heroui/shared-icons@5804

@heroui/shared-utils

npm i https://pkg.pr.new/@heroui/shared-utils@5804

@heroui/stories-utils

npm i https://pkg.pr.new/@heroui/stories-utils@5804

@heroui/test-utils

npm i https://pkg.pr.new/@heroui/test-utils@5804

commit: e126a87

Copy link
Member

@wingkwong wingkwong left a comment

Choose a reason for hiding this comment

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

  1. please don't include other changes like date range picker which is not related in this PR
  2. please add a changeset
  3. eslint fails
  4. your PR changes the existing behaviour. With animation (default setting), try create a toast and wait for timeout. You will see the animation is gone when closing. The expected result should be same as closing the toast manually.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants