Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 75 additions & 63 deletions apps/www/src/components/playground/toast-examples.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,85 @@
'use client';

import { Button, Flex, ToastContainer, toast } from '@raystack/apsara';
import { Button, Flex, Toast, toastManager } from '@raystack/apsara';
import PlaygroundLayout from './playground-layout';

export function ToastExamples() {
return (
<PlaygroundLayout title='Toast'>
<ToastContainer />
<Flex gap='large' wrap='wrap'>
<Button
onClick={() =>
toast.success('This is a toast', { position: 'bottom-left' })
}
>
Bottom Left Toast
</Button>
<Button
onClick={() =>
toast.success('This is a toast', { position: 'bottom-center' })
}
>
Bottom Center Toast
</Button>
<Button
onClick={() =>
toast.success('This is a toast', { position: 'bottom-right' })
}
>
Bottom Right Toast
</Button>
</Flex>
<Flex gap='large' wrap='wrap'>
<Button
onClick={() =>
toast.success('This is a toast', { position: 'top-left' })
}
>
Top Left Toast
</Button>
<Button
onClick={() =>
toast.success('This is a toast', { position: 'top-center' })
}
>
Top Center Toast
</Button>
<Button
onClick={() =>
toast.success('This is a toast', { position: 'top-right' })
}
>
Top Right Toast
</Button>
</Flex>
<Button
variant='outline'
onClick={() =>
toast.success('Data loaded successfully.', {
dismissible: true,
action: (
<Button size='small' onClick={() => console.log('Toast appears')}>
Click Me
</Button>
)
})
}
>
Action Toast
</Button>
<Toast.Provider position='bottom-right'>
<Flex gap='large' wrap='wrap'>
<Button
onClick={() =>
toastManager.add({ title: 'Success toast', type: 'success' })
}
>
Success Toast
</Button>
<Button
onClick={() =>
toastManager.add({ title: 'Error toast', type: 'error' })
}
>
Error Toast
</Button>
<Button
onClick={() =>
toastManager.add({ title: 'Warning toast', type: 'warning' })
}
>
Warning Toast
</Button>
<Button
onClick={() =>
toastManager.add({ title: 'Info toast', type: 'info' })
}
>
Info Toast
</Button>
</Flex>
<Flex gap='large' wrap='wrap'>
<Button
onClick={() =>
toastManager.add({
title: 'With description',
description: 'This toast has a title and a description.',
type: 'success'
})
}
>
Description Toast
</Button>
<Button
onClick={() =>
toastManager.add({
title: 'Item deleted',
description: '1 item was moved to trash.',
actionProps: {
children: 'Undo',
onClick: () => console.log('Undo clicked')
}
})
}
>
Action Toast
</Button>
<Button
variant='outline'
onClick={() =>
toastManager.promise(
new Promise(resolve => setTimeout(resolve, 2000)),
{
loading: 'Loading...',
success: 'Done!',
error: 'Failed!'
}
)
}
>
Promise Toast
</Button>
</Flex>
</Toast.Provider>
</PlaygroundLayout>
);
}
204 changes: 196 additions & 8 deletions apps/www/src/content/docs/components/toast/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,201 @@
export const preview = {
type: 'code',
code: `
function ToastTest(){
return <div>
<ToastContainer />
<Button
onClick={() => toast.success("This is a toast")}>
Trigger toast
function ToastPreview() {
return (
<Toast.Provider>
<Flex gap="medium" wrap="wrap">
<Button onClick={() => toastManager.add({ title: "This is a toast", type: "success" })}>
Trigger toast
</Button>
</Flex>
</Toast.Provider>
)
}`
};

export const basicDemo = {
type: 'code',
code: `
<Button onClick={() => toastManager.add({ title: "Hello from Apsara!" })}>
Show toast
</Button>`
};

export const typesDemo = {
type: 'code',
tabs: [
{
name: 'Success',
code: `
<Button onClick={() => toastManager.add({ title: "Saved successfully", type: "success" })}>
Success
</Button>`
},
{
name: 'Error',
code: `
<Button onClick={() => toastManager.add({ title: "Something went wrong", type: "error" })}>
Error
</Button>`
},
{
name: 'Warning',
code: `
<Button onClick={() => toastManager.add({ title: "Heads up!", type: "warning" })}>
Warning
</Button>`
},
{
name: 'Info',
code: `
<Button onClick={() => toastManager.add({ title: "FYI: System update available", type: "info" })}>
Info
</Button>`
},
{
name: 'Loading',
code: `
<Button onClick={() => toastManager.add({ title: "Processing...", type: "loading", timeout: 0 })}>
Loading
</Button>`
}
]
};

export const descriptionDemo = {
type: 'code',
code: `
<Flex gap="medium" wrap="wrap">
<Button onClick={() => toastManager.add({
title: "File uploaded",
description: "Your document has been uploaded successfully.",
type: "success"
})}>
With description
</Button>
<Button onClick={() => toastManager.add({
title: "Connection lost",
description: "Please check your internet connection and try again.",
type: "error"
})}>
Error with description
</Button>
</div>
}`
</Flex>`
};

export const actionDemo = {
type: 'code',
code: `
<Button onClick={() => toastManager.add({
title: "Item deleted",
description: "1 item was moved to trash.",
actionProps: {
children: "Undo",
onClick: () => toastManager.add({ title: "Item restored", type: "success" })
}
})}>
Action toast
</Button>`
};

export const promiseDemo = {
type: 'code',
tabs: [
{
name: 'Basic',
code: `
<Button onClick={() => {
const promise = new Promise((resolve) => setTimeout(resolve, 2000));
toastManager.promise(promise, {
loading: "Loading data...",
success: "Data loaded successfully!",
error: "Failed to load data."
});
}}>
Promise toast
</Button>`
},
{
name: 'With options',
code: `
<Button onClick={() => {
const promise = new Promise((resolve) => setTimeout(resolve, 2000));
toastManager.promise(promise, {
loading: { title: "Saving", description: "Please wait..." },
success: { title: "Saved", description: "Document saved.", type: "success" },
error: { title: "Failed", description: "Could not save document.", type: "error" }
});
}}>
Promise with options
</Button>`
}
]
};

export const positionDemo = {
type: 'code',
tabs: [
{
name: 'Bottom Right',
code: `
<Button onClick={() => toastManager.add({ title: "Bottom right toast" })}>
Bottom Right
</Button>`
},
{
name: 'Top Center',
code: `
<Button onClick={() => toastManager.add({ title: "Top center toast" })}>
Top Center
</Button>`
},
{
name: 'Top Right',
code: `
<Button onClick={() => toastManager.add({ title: "Top right toast" })}>
Top Right
</Button>`
},
{
name: 'Bottom Left',
code: `
<Button onClick={() => toastManager.add({ title: "Bottom left toast" })}>
Bottom Left
</Button>`
}
]
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.

export const updateDemo = {
type: 'code',
code: `
function UpdateToast() {
const idRef = React.useRef(null);
return (
<Flex gap="medium" wrap="wrap">
<Button onClick={() => {
idRef.current = toastManager.add({ title: "Processing...", type: "loading", timeout: 0 });
}}>
Comment on lines +247 to +249
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Don't orphan persistent toasts on repeated starts.

Line 248 always adds a new timeout: 0 toast and overwrites the only tracked id. A second click leaves the earlier toast open with no way for the “Mark as done” or “Dismiss” buttons to reach it.

Suggested fix
         <Button onClick={() => {
+          if (idRef.current) return;
           idRef.current = toastManager.add({ title: "Processing...", type: "loading", timeout: 0 });
         }}>
📝 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
<Button onClick={() => {
idRef.current = toastManager.add({ title: "Processing...", type: "loading", timeout: 0 });
}}>
<Button onClick={() => {
if (idRef.current) return;
idRef.current = toastManager.add({ title: "Processing...", type: "loading", timeout: 0 });
}}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/toast/demo.ts` around lines 247 - 249,
The onClick handler for the Button always creates a new persistent toast via
toastManager.add({..., timeout: 0}) and overwrites idRef.current, orphaning
earlier toasts; update the handler in the Button onClick to first check
idRef.current and either reuse/update the existing toast (via
toastManager.update or similar) or dismiss it (via toastManager.remove/close)
before calling toastManager.add and assigning idRef.current, so the “Mark as
done” and “Dismiss” buttons can target the correct toast.

Start processing
</Button>
<Button variant="outline" onClick={() => {
if (idRef.current) {
toastManager.update(idRef.current, { title: "Done!", type: "success", timeout: 3000 });
Comment on lines +243 to +254
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

In React + TypeScript, what type does useRef(null)infer, and do you needuseRef<string | null>(null)before assigning a string toref.current?

💡 Result:

In React’s TypeScript typings, useRef has an overload specifically for “refs that start as null”:

  • function useRef<T>(initialValue: T): RefObject<T>
  • function useRef<T>(initialValue: T | null): RefObject<T> (raw.githubusercontent.com)

So when you write:

const ref = useRef(null);

TypeScript picks the T | null overload, and with only null to infer from, it infers T as never (effectively: “this ref will never hold a non-null value”), giving you RefObject<never> / ref.current that you can’t assign a string to. (raw.githubusercontent.com)

Yes—you should provide a type if you plan to assign a string later:

const ref = useRef<string | null>(null);
ref.current = "hello"; // OK

(Alternative: if you know it won’t be null after initialization, you can use useRef<string>(null!).) (sobyte.net)

Citations:


🏁 Script executed:

#!/bin/bash
# Check if the file exists and read the specified lines
if [ -f "apps/www/src/content/docs/components/toast/demo.ts" ]; then
  echo "=== File exists ==="
  # Get line count to verify range
  total_lines=$(wc -l < "apps/www/src/content/docs/components/toast/demo.ts")
  echo "Total lines: $total_lines"
  
  # Read lines 175-186
  echo ""
  echo "=== Lines 175-186 ==="
  sed -n '175,186p' "apps/www/src/content/docs/components/toast/demo.ts"
else
  echo "File not found at apps/www/src/content/docs/components/toast/demo.ts"
fi

Repository: raystack/apsara

Length of output: 607


Type idRef to the returned toast ID.

toastManager.add() returns a string, but React.useRef(null) leaves this example with an untyped ref under strict TS. Use React.useRef<string | null>(null) before assigning IDs into current.

Suggested fix
-    const idRef = React.useRef(null);
+    const idRef = React.useRef<string | null>(null);
📝 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
function UpdateToast() {
const idRef = React.useRef(null);
return (
<Flex gap="medium" wrap="wrap">
<Button onClick={() => {
idRef.current = toastManager.add({ title: "Processing...", type: "loading", timeout: 0 });
}}>
Start processing
</Button>
<Button variant="outline" onClick={() => {
if (idRef.current) {
toastManager.update(idRef.current, { title: "Done!", type: "success", timeout: 3000 });
function UpdateToast() {
const idRef = React.useRef<string | null>(null);
return (
<Flex gap="medium" wrap="wrap">
<Button onClick={() => {
idRef.current = toastManager.add({ title: "Processing...", type: "loading", timeout: 0 });
}}>
Start processing
</Button>
<Button variant="outline" onClick={() => {
if (idRef.current) {
toastManager.update(idRef.current, { title: "Done!", type: "success", timeout: 3000 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/www/src/content/docs/components/toast/demo.ts` around lines 175 - 186,
The ref idRef in UpdateToast is untyped (React.useRef(null)) but
toastManager.add() returns a string; change idRef to be typed as
React.useRef<string | null>(null) so assigning the returned toast ID and later
null checks (if (idRef.current) ...) are type-safe; update any other references
to idRef.current in UpdateToast to respect the string | null type.

idRef.current = null;
}
}}>
Mark as done
</Button>
<Button variant="outline" onClick={() => {
if (idRef.current) {
toastManager.close(idRef.current);
idRef.current = null;
}
}}>
Dismiss
</Button>
</Flex>
)
}`
};
Loading
Loading