Skip to content

Commit 9a9e976

Browse files
author
ci-bot
committed
toast layout
1 parent 4553b74 commit 9a9e976

File tree

5 files changed

+136
-24
lines changed

5 files changed

+136
-24
lines changed

apps/remix-ide/src/app/plugins/notification.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ interface INotificationApi {
1111
methods: {
1212
modal: (args: AppModal) => void
1313
alert: (args: AlertModal) => void
14-
toast: (message: string) => void
14+
toast: (message: string) => number
1515
hideToaster: (id: number) => void
1616
}
1717
}
@@ -43,12 +43,13 @@ export class NotificationPlugin extends Plugin implements MethodApi<INotificatio
4343
return this.dispatcher.alert(args)
4444
}
4545

46-
async toast(message: string | JSX.Element, timeout?: number) {
47-
this.toastId++
48-
return this.dispatcher.toast(message, timeout, this.toastId)
46+
async toast(message: string | JSX.Element, timeout?: number, timestamp?: number): Promise<number> {
47+
timestamp = timestamp || Date.now()
48+
this.dispatcher.toast(message, timeout, timestamp)
49+
return timestamp
4950
}
5051

5152
async hideToaster(id: number) {
52-
toast.dismiss(id)
53+
toast.dismiss('toast-' + id)
5354
}
5455
}

libs/remix-ui/app/src/lib/remix-app/components/modals/dialogs.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
1-
import React from 'react'
1+
import React, { useMemo } from 'react'
22
import { useDialogDispatchers, useDialogs } from '../../context/provider'
3-
import { Toaster } from '@remix-ui/toaster'
3+
import { ToasterContainer } from '@remix-ui/toaster'
44
import ModalWrapper from './modal-wrapper'
55

66
const AppDialogs = () => {
77
const { handleHideModal, handleToaster } = useDialogDispatchers()
8-
const { focusModal, focusToaster } = useDialogs()
8+
const { focusModal, toasters } = useDialogs()
9+
10+
// Map toasters to ToasterProps format with useMemo to prevent recreating on every render
11+
const toastList = useMemo(() => {
12+
return toasters.map((toaster) => ({
13+
message: toaster.message,
14+
id: toaster.toastId || `toast-${toaster.timestamp}`,
15+
timeout: toaster.timeout,
16+
timestamp: toaster.timestamp,
17+
handleHide: handleToaster
18+
}))
19+
}, [toasters, handleToaster])
920

1021
return (
1122
<>
1223
<ModalWrapper {...focusModal} handleHide={handleHideModal}></ModalWrapper>
13-
<Toaster message={focusToaster.message} id={focusToaster.toastId} timeout={focusToaster.timeout} timestamp={focusToaster.timestamp} handleHide={handleToaster} />
24+
<ToasterContainer toasts={toastList} />
1425
</>
1526
)
1627
}

libs/remix-ui/app/src/lib/remix-app/context/provider.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,11 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
6969
})
7070
}
7171

72-
const toast = (message: string | JSX.Element, timeout?: number) => {
72+
const toast = (message: string | JSX.Element, timeout?: number, timestamp?: number) => {
73+
timestamp = timestamp || Date.now()
7374
dispatch({
7475
type: modalActionTypes.setToast,
75-
payload: { message, timestamp: Date.now(), timeout }
76+
payload: { message, timestamp, timeout }
7677
})
7778
}
7879

libs/remix-ui/toaster/src/lib/toaster.css

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
min-width: 350px;
88
max-width: 600px;
99
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
10+
word-wrap: break-word;
11+
overflow-wrap: break-word;
12+
white-space: normal;
13+
line-height: 1.4;
1014
}
1115

1216
.remixui_custom_toast {
@@ -18,14 +22,30 @@
1822
/* Override Sonner default styles to match Remix theme */
1923
[data-sonner-toaster] {
2024
z-index: 1001 !important;
25+
display: flex;
26+
flex-direction: column;
2127
}
2228

2329
[data-sonner-toast] {
24-
background:var(--bs-primary, #212529) !important;
25-
color: var(--bs-info-text-emphasis, #212529) !important;
30+
background: var(--bs-light, #f8f9fa) !important;
31+
color: var(--bs-dark, #212529) !important;
2632
border: 0px solid var(--bs-dark, #6c757d) !important;
2733
font-weight: 700 !important;
2834
font-size: 19px !important;
35+
word-wrap: break-word;
36+
overflow-wrap: break-word;
37+
opacity: 1 !important;
38+
visibility: visible !important;
39+
pointer-events: auto !important;
40+
position: relative;
41+
width: 100%;
42+
margin-bottom: 12px;
43+
transform: none !important;
44+
}
45+
46+
[data-sonner-toast]:hover {
47+
transform: none !important;
48+
scale: 1 !important;
2949
}
3050

3151
[data-sonner-toast][data-styled="true"] {
@@ -50,8 +70,8 @@
5070
right: 12px;
5171
background: transparent !important;
5272
border: none;
53-
color: var(--bs-info-text-emphasis, #212529) !important;
54-
opacity: 0.5;
73+
color: var(--bs-dark, #212529) !important;
74+
opacity: 0.7;
5575
cursor: pointer;
5676
padding: 4px;
5777
display: flex;
@@ -61,6 +81,7 @@
6181
transition: all 0.2s ease;
6282
width: 24px;
6383
height: 24px;
84+
z-index: 1;
6485
}
6586

6687
[data-sonner-toast] [data-close-button]:hover {

libs/remix-ui/toaster/src/lib/toaster.tsx

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,88 @@ export interface ToasterProps {
1616
onToastCreated?: (toastId: string | number) => void
1717
}
1818

19+
export interface ToasterContainerProps {
20+
toasts: ToasterProps[]
21+
}
22+
23+
// Individual toast trigger component (no UI, just triggers toast)
24+
export const ToastTrigger = (props: ToasterProps) => {
25+
const mountedRef = React.useRef(false)
26+
27+
useEffect(() => {
28+
// Only trigger on mount, not on updates
29+
if (!mountedRef.current && props.message && props.id) {
30+
mountedRef.current = true
31+
32+
// Show toast using Sonner - Sonner handles deduplication via ID automatically
33+
const duration = props.timeout || 2000
34+
const showCloseButton = duration > 5000
35+
36+
if (typeof props.message === 'string') {
37+
const toastId = toast(props.message, {
38+
id: props.id,
39+
unstyled: true,
40+
duration,
41+
closeButton: showCloseButton,
42+
onDismiss: () => {
43+
props.handleHide && props.handleHide()
44+
},
45+
onAutoClose: () => {
46+
props.handleHide && props.handleHide()
47+
}
48+
})
49+
console.log('toastId', toastId, props.id)
50+
} else {
51+
// For JSX elements, use toast.custom
52+
const toastId = toast.custom(
53+
() => (
54+
<div className="remixui_sonner_toast alert alert-info bg-light">
55+
{props.message}
56+
</div>
57+
),
58+
{
59+
id: props.id,
60+
duration,
61+
closeButton: showCloseButton,
62+
onDismiss: () => {
63+
props.handleHide && props.handleHide()
64+
},
65+
onAutoClose: () => {
66+
props.handleHide && props.handleHide()
67+
}
68+
}
69+
)
70+
console.log('toastId', toastId, props.id)
71+
}
72+
}
73+
}, [])
74+
75+
return null
76+
}
77+
78+
// Container component that renders the Sonner toaster and all toast triggers
79+
export const ToasterContainer = (props: ToasterContainerProps) => {
80+
return (
81+
<>
82+
<SonnerToaster
83+
position="top-right"
84+
gap={12}
85+
toastOptions={{
86+
className: 'remixui_sonner_toast alert alert-info bg-light',
87+
unstyled: true
88+
}}
89+
/>
90+
{props.toasts.map((toastProps) => (
91+
<ToastTrigger
92+
key={toastProps.id || toastProps.timestamp}
93+
{...toastProps}
94+
/>
95+
))}
96+
</>
97+
)
98+
}
99+
100+
// Legacy component for backward compatibility
19101
export const Toaster = (props: ToasterProps) => {
20102
useEffect(() => {
21103
if (props.message) {
@@ -26,6 +108,7 @@ export const Toaster = (props: ToasterProps) => {
26108
let toastId: string | number
27109

28110
if (typeof props.message === 'string') {
111+
29112
toastId = toast(props.message, {
30113
id: props.id,
31114
unstyled: true,
@@ -38,6 +121,7 @@ export const Toaster = (props: ToasterProps) => {
38121
props.handleHide && props.handleHide()
39122
}
40123
})
124+
console.log('toastId', toastId, props.id)
41125
} else {
42126
// For JSX elements, use toast.custom
43127
toastId = toast.custom(
@@ -58,6 +142,7 @@ export const Toaster = (props: ToasterProps) => {
58142
}
59143
}
60144
)
145+
console.log('toastId', toastId, props.id)
61146
}
62147

63148
// Call the callback with the toast ID so caller can dismiss it later
@@ -67,15 +152,8 @@ export const Toaster = (props: ToasterProps) => {
67152
}
68153
}, [props.message, props.timestamp])
69154

70-
return (
71-
<SonnerToaster
72-
position="top-right"
73-
toastOptions={{
74-
className: 'remixui_sonner_toast alert alert-info bg-light',
75-
unstyled: true
76-
}}
77-
/>
78-
)
155+
return <div></div>
79156
}
80157

158+
81159
export default Toaster

0 commit comments

Comments
 (0)