From f2a4315c6cc73a2e5281591bae94ff64f47160f5 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Wed, 12 Nov 2025 12:44:14 +0100 Subject: [PATCH 01/16] improve toaster --- libs/remix-ui/toaster/src/lib/toaster.css | 71 +++++---- libs/remix-ui/toaster/src/lib/toaster.tsx | 170 +++++----------------- package.json | 1 + yarn.lock | 5 + 4 files changed, 79 insertions(+), 168 deletions(-) diff --git a/libs/remix-ui/toaster/src/lib/toaster.css b/libs/remix-ui/toaster/src/lib/toaster.css index c026edbf492..5980783fdbb 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.css +++ b/libs/remix-ui/toaster/src/lib/toaster.css @@ -1,43 +1,40 @@ -.remixui_tooltip { - z-index: 1001; - display: flex; - justify-content: space-between; - align-items: center; - position: fixed; - min-height: 50px; - padding: 16px 24px 12px; - border-radius: 3px; - left: 40%; - font-size: 14px; - text-align: center; - bottom: -0px; - flex-direction: row; +/* Sonner toast styling */ +.remixui_sonner_toast { + border-radius: 8px; + padding: 16px 24px; + font-size: 14px; + min-width: 300px; + max-width: 500px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } -@-webkit-keyframes remixui_animatebottom { - 0% {bottom: -300px} - 100% {bottom: 0px} + +.remixui_custom_toast { + display: flex; + align-items: center; + gap: 12px; } -@keyframes remixui_animatebottom { - 0% {bottom: -300px} - 100% {bottom: 0px} + +/* Override Sonner default styles to match Remix theme */ +[data-sonner-toaster] { + z-index: 1001 !important; } -@-webkit-keyframes remixui_animatetop { - 0% {bottom: 0px} - 100% {bottom: -300px} + +[data-sonner-toast] { + background: var(--bs-body-bg, #f8f9fa) !important; + color: var(--bs-dark, #212529) !important; + border: 0px solid var(--bs-dark, #6c757d) !important; + font-weight: 500; } -@keyframes remixui_animatetop { - 0% {bottom: 0px} - 100% {bottom: -300px} + +[data-sonner-toast][data-styled="true"] { + padding: 16px 24px !important; + gap: 12px !important; } -.remixui_animateTop { - -webkit-animation-name: remixui_animatetop; - -webkit-animation-duration: 2s; - animation-name: remixui_animatetop; - animation-duration: 2s; -} -.remixui_animateBottom { - -webkit-animation-name: remixui_animatebottom; - -webkit-animation-duration: 2s; - animation-name: remixui_animatebottom; - animation-duration: 2s; + +/* Dark theme support */ +[data-sonner-toast][data-theme="dark"], +.dark [data-sonner-toast] { + background: var(--bs-dark, #212529) !important; + color: var(--bs-light, #f8f9fa) !important; + border: 0px solid var(--bs-secondary, #6c757d) !important; } diff --git a/libs/remix-ui/toaster/src/lib/toaster.tsx b/libs/remix-ui/toaster/src/lib/toaster.tsx index 0870f4515db..1f94d8d43e7 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.tsx +++ b/libs/remix-ui/toaster/src/lib/toaster.tsx @@ -1,5 +1,5 @@ -import React, {useEffect, useState} from 'react' // eslint-disable-line -import {ModalDialog} from '@remix-ui/modal-dialog' // eslint-disable-line +import React, {useEffect} from 'react' // eslint-disable-line +import {Toaster as SonnerToaster, toast} from 'sonner' import './toaster.css' @@ -12,143 +12,51 @@ export interface ToasterProps { } export const Toaster = (props: ToasterProps) => { - const [state, setState] = useState<{ - message: string | JSX.Element - hide: boolean - hiding: boolean - timeOutId: any - timeOut: number - showModal: boolean - showFullBtn: boolean - }>({ - message: '', - hide: true, - hiding: false, - timeOutId: null, - timeOut: props.timeOut || 7000, - showModal: false, - showFullBtn: false - }) - useEffect(() => { if (props.message) { - const timeOutId = setTimeout(() => { - setState((prevState) => { - return { ...prevState, hiding: true } - }) - }, state.timeOut) - - setState((prevState) => { - if (typeof props.message === 'string' && props.message.length > 201) { - const shortTooltipText = props.message.substring(0, 200) + '...' - - return { - ...prevState, - hide: false, - hiding: false, - timeOutId, - message: shortTooltipText + // Show toast using Sonner + const duration = props.timeOut || 2000 + + if (typeof props.message === 'string') { + toast(props.message, { + duration, + onDismiss: () => { + props.handleHide && props.handleHide() + }, + onAutoClose: () => { + props.handleHide && props.handleHide() } - } else { - const shortTooltipText = props.message - - return { - ...prevState, - hide: false, - hiding: false, - timeOutId, - message: shortTooltipText + }) + } else { + // For JSX elements, use toast.custom + toast.custom( + () => ( +
+ {props.message} +
+ ), + { + duration, + onDismiss: () => { + props.handleHide && props.handleHide() + }, + onAutoClose: () => { + props.handleHide && props.handleHide() + } } - } - }) - } - }, [props.message, props.timestamp]) - - useEffect(() => { - if (state.hiding) { - setTimeout(() => { - closeTheToaster() - }, 1800) - } - }, [state.hiding]) - - const showFullMessage = () => { - setState((prevState) => { - return { ...prevState, showModal: true } - }) - } - - const hideFullMessage = () => { - //eslint-disable-line - setState((prevState) => { - return { ...prevState, showModal: false } - }) - } - - const closeTheToaster = () => { - if (state.timeOutId) { - clearTimeout(state.timeOutId) - } - props.handleHide && props.handleHide() - setState((prevState) => { - return { - ...prevState, - message: '', - hide: true, - hiding: false, - timeOutId: null, - showModal: false + ) } - }) - } - - const handleMouseEnter = () => { - if (state.timeOutId) { - clearTimeout(state.timeOutId) } - setState((prevState) => { - return { ...prevState, timeOutId: null } - }) - } - - const handleMouseLeave = () => { - if (!state.timeOutId) { - const timeOutId = setTimeout(() => { - setState((prevState) => { - return { ...prevState, hiding: true } - }) - }, state.timeOut) - - setState((prevState) => { - return { ...prevState, timeOutId } - }) - } - } + }, [props.message, props.timestamp]) return ( - <> - {}} hide={!state.showModal} handleHide={hideFullMessage} /> - {!state.hide && ( -
- - {state.message} - {state.showFullBtn && ( - - )} - - - - -
- )} - + ) } diff --git a/package.json b/package.json index 7d1563ffa6a..1409edf2fd7 100644 --- a/package.json +++ b/package.json @@ -238,6 +238,7 @@ "sol2uml": "^2.4.3", "solhint": "^3.4.1", "solidity-comments-extractor": "^0.0.8", + "sonner": "^2.0.7", "string-similarity": "^4.0.4", "svg2pdf.js": "^2.2.1", "text-encoding": "^0.7.0", diff --git a/yarn.lock b/yarn.lock index fc02d116a65..96215653679 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29354,6 +29354,11 @@ sonic-boom@^2.2.1: dependencies: atomic-sleep "^1.0.0" +sonner@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/sonner/-/sonner-2.0.7.tgz#810c1487a67ec3370126e0f400dfb9edddc3e4f6" + integrity sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w== + sort-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" From d75c4d56d9bd945505bf0a21fd76bff39e8aef86 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Tue, 25 Nov 2025 12:10:09 +0100 Subject: [PATCH 02/16] top right corner --- libs/remix-ui/toaster/src/lib/toaster.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/remix-ui/toaster/src/lib/toaster.tsx b/libs/remix-ui/toaster/src/lib/toaster.tsx index 1f94d8d43e7..5350a4e958e 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.tsx +++ b/libs/remix-ui/toaster/src/lib/toaster.tsx @@ -51,8 +51,7 @@ export const Toaster = (props: ToasterProps) => { return ( Date: Wed, 26 Nov 2025 13:13:50 +0100 Subject: [PATCH 03/16] fix styling --- libs/remix-ui/toaster/src/lib/toaster.css | 20 ++++++++++++-------- libs/remix-ui/toaster/src/lib/toaster.tsx | 8 +++++--- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/libs/remix-ui/toaster/src/lib/toaster.css b/libs/remix-ui/toaster/src/lib/toaster.css index 5980783fdbb..a078489438a 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.css +++ b/libs/remix-ui/toaster/src/lib/toaster.css @@ -1,10 +1,11 @@ /* Sonner toast styling */ .remixui_sonner_toast { border-radius: 8px; - padding: 16px 24px; - font-size: 14px; - min-width: 300px; - max-width: 500px; + padding: 20px 28px; + font-weight: 700 !important; + font-size: 19px !important; + min-width: 350px; + max-width: 600px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } @@ -20,15 +21,17 @@ } [data-sonner-toast] { - background: var(--bs-body-bg, #f8f9fa) !important; - color: var(--bs-dark, #212529) !important; + background:var(--bs-primary, #212529) !important; + color: var(--bs-info-text-emphasis, #212529) !important; border: 0px solid var(--bs-dark, #6c757d) !important; - font-weight: 500; + font-weight: 700 !important; + font-size: 19px !important; } [data-sonner-toast][data-styled="true"] { - padding: 16px 24px !important; + padding: 20px 28px !important; gap: 12px !important; + font-weight: 700 !important; } /* Dark theme support */ @@ -37,4 +40,5 @@ background: var(--bs-dark, #212529) !important; color: var(--bs-light, #f8f9fa) !important; border: 0px solid var(--bs-secondary, #6c757d) !important; + font-weight: 700 !important; } diff --git a/libs/remix-ui/toaster/src/lib/toaster.tsx b/libs/remix-ui/toaster/src/lib/toaster.tsx index 5350a4e958e..e3050a21e2c 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.tsx +++ b/libs/remix-ui/toaster/src/lib/toaster.tsx @@ -15,10 +15,11 @@ export const Toaster = (props: ToasterProps) => { useEffect(() => { if (props.message) { // Show toast using Sonner - const duration = props.timeOut || 2000 + const duration = props.timeOut || 120000 if (typeof props.message === 'string') { toast(props.message, { + unstyled: true, duration, onDismiss: () => { props.handleHide && props.handleHide() @@ -31,7 +32,7 @@ export const Toaster = (props: ToasterProps) => { // For JSX elements, use toast.custom toast.custom( () => ( -
+
{props.message}
), @@ -53,7 +54,8 @@ export const Toaster = (props: ToasterProps) => { ) From 9cd31302f13fe20220a157231ef2a2ea45ad391b Mon Sep 17 00:00:00 2001 From: ci-bot Date: Thu, 27 Nov 2025 13:13:57 +0100 Subject: [PATCH 04/16] close button --- libs/remix-ui/toaster/src/lib/toaster.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/remix-ui/toaster/src/lib/toaster.tsx b/libs/remix-ui/toaster/src/lib/toaster.tsx index e3050a21e2c..e26437d4b35 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.tsx +++ b/libs/remix-ui/toaster/src/lib/toaster.tsx @@ -16,11 +16,13 @@ export const Toaster = (props: ToasterProps) => { if (props.message) { // Show toast using Sonner const duration = props.timeOut || 120000 + const showCloseButton = duration > 5000 if (typeof props.message === 'string') { toast(props.message, { unstyled: true, duration, + closeButton: showCloseButton, onDismiss: () => { props.handleHide && props.handleHide() }, @@ -38,6 +40,7 @@ export const Toaster = (props: ToasterProps) => { ), { duration, + closeButton: showCloseButton, onDismiss: () => { props.handleHide && props.handleHide() }, From 4553b748624c6265b070ff95163e7536c7304310 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Thu, 27 Nov 2025 14:26:37 +0100 Subject: [PATCH 05/16] timeout and toastid --- .../src/app/plugins/notification.tsx | 16 +++++-- .../app/src/lib/remix-app/actions/modals.ts | 2 +- .../remix-app/components/modals/dialogs.tsx | 2 +- .../app/src/lib/remix-app/context/context.tsx | 4 +- .../src/lib/remix-app/context/provider.tsx | 4 +- .../app/src/lib/remix-app/interface/index.ts | 4 +- .../app/src/lib/remix-app/state/modals.ts | 2 +- libs/remix-ui/toaster/src/lib/toaster.css | 42 +++++++++++++++++++ libs/remix-ui/toaster/src/lib/toaster.tsx | 22 ++++++++-- 9 files changed, 81 insertions(+), 17 deletions(-) diff --git a/apps/remix-ide/src/app/plugins/notification.tsx b/apps/remix-ide/src/app/plugins/notification.tsx index c37b74d0a61..a62bae72fa2 100644 --- a/apps/remix-ide/src/app/plugins/notification.tsx +++ b/apps/remix-ide/src/app/plugins/notification.tsx @@ -4,6 +4,7 @@ import { LibraryProfile, MethodApi, StatusEvents } from '@remixproject/plugin-ut import { AppModal } from '@remix-ui/app' import { AlertModal } from '@remix-ui/app' import { dispatchModalInterface } from '@remix-ui/app' +import { Toaster, toast } from '@remix-ui/toaster' interface INotificationApi { events: StatusEvents @@ -11,7 +12,7 @@ interface INotificationApi { modal: (args: AppModal) => void alert: (args: AlertModal) => void toast: (message: string) => void - + hideToaster: (id: number) => void } } @@ -19,13 +20,15 @@ const profile: LibraryProfile = { name: 'notification', displayName: 'Notification', description: 'Displays notifications', - methods: ['modal', 'alert', 'toast'] + methods: ['modal', 'alert', 'toast', 'hideToaster'] } export class NotificationPlugin extends Plugin implements MethodApi { dispatcher: dispatchModalInterface + toastId: number constructor() { super(profile) + this.toastId = 0 } setDispatcher(dispatcher: dispatchModalInterface) { @@ -40,7 +43,12 @@ export class NotificationPlugin extends Plugin implements MethodApi { return ( <> - + ) } diff --git a/libs/remix-ui/app/src/lib/remix-app/context/context.tsx b/libs/remix-ui/app/src/lib/remix-app/context/context.tsx index f7ef62c987b..4037f10f3c6 100644 --- a/libs/remix-ui/app/src/lib/remix-app/context/context.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/context/context.tsx @@ -26,7 +26,7 @@ export const platformContext = React.createContext(null) export interface dispatchModalInterface { modal: (data: AppModal) => void - toast: (message: string | JSX.Element) => void + toast: (message: string | JSX.Element, timeout?: number, toastId?: number) => void alert: (data: AlertModal) => void handleHideModal: () => void handleToaster: () => void @@ -34,7 +34,7 @@ export interface dispatchModalInterface { export const dispatchModalContext = React.createContext({ modal: (data: AppModal) => {}, - toast: (message: string | JSX.Element) => {}, + toast: (message: string | JSX.Element, timeout?: number, toastId?: number) => {}, alert: (data: AlertModal) => {}, handleHideModal: () => {}, handleToaster: () => {} diff --git a/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx b/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx index 4d68896c30f..ffc9cb43c5d 100644 --- a/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx @@ -69,10 +69,10 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt }) } - const toast = (message: string | JSX.Element) => { + const toast = (message: string | JSX.Element, timeout?: number) => { dispatch({ type: modalActionTypes.setToast, - payload: { message, timestamp: Date.now() } + payload: { message, timestamp: Date.now(), timeout } }) } diff --git a/libs/remix-ui/app/src/lib/remix-app/interface/index.ts b/libs/remix-ui/app/src/lib/remix-app/interface/index.ts index 72d8643f219..5429226e8d1 100644 --- a/libs/remix-ui/app/src/lib/remix-app/interface/index.ts +++ b/libs/remix-ui/app/src/lib/remix-app/interface/index.ts @@ -40,9 +40,9 @@ export interface AlertModal { export interface ModalState { modals: AppModal[], - toasters: {message: (string | JSX.Element), timestamp: number }[], + toasters: {message: (string | JSX.Element), timestamp: number, timeout?: number, toastId?: number }[], focusModal: AppModal, - focusToaster: {message: (string | JSX.Element), timestamp: number }, + focusToaster: {message: (string | JSX.Element), timestamp: number, timeout?: number, toastId?: number } focusTemplateExplorer: GenericModal } diff --git a/libs/remix-ui/app/src/lib/remix-app/state/modals.ts b/libs/remix-ui/app/src/lib/remix-app/state/modals.ts index 41189f69e88..f398b7b3123 100644 --- a/libs/remix-ui/app/src/lib/remix-app/state/modals.ts +++ b/libs/remix-ui/app/src/lib/remix-app/state/modals.ts @@ -17,7 +17,7 @@ export const ModalInitialState: ModalState = { cancelFn: () => { }, showCancelIcon: false }, - focusToaster: { message: '', timestamp: 0 }, + focusToaster: { message: '', timestamp: 0, timeout: 2000 }, focusTemplateExplorer: { id: '', hide: true, diff --git a/libs/remix-ui/toaster/src/lib/toaster.css b/libs/remix-ui/toaster/src/lib/toaster.css index a078489438a..0e33414d0a5 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.css +++ b/libs/remix-ui/toaster/src/lib/toaster.css @@ -42,3 +42,45 @@ border: 0px solid var(--bs-secondary, #6c757d) !important; font-weight: 700 !important; } + +/* Close button styling */ +[data-sonner-toast] [data-close-button] { + position: absolute; + top: 12px; + right: 12px; + background: transparent !important; + border: none; + color: var(--bs-info-text-emphasis, #212529) !important; + opacity: 0.5; + cursor: pointer; + padding: 4px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: all 0.2s ease; + width: 24px; + height: 24px; +} + +[data-sonner-toast] [data-close-button]:hover { + opacity: 1; + background: rgba(0, 0, 0, 0.05) !important; + transform: scale(1.1); +} + +[data-sonner-toast] [data-close-button] svg { + width: 16px; + height: 16px; +} + +/* Dark theme close button */ +[data-sonner-toast][data-theme="dark"] [data-close-button], +.dark [data-sonner-toast] [data-close-button] { + color: var(--bs-light, #f8f9fa) !important; +} + +[data-sonner-toast][data-theme="dark"] [data-close-button]:hover, +.dark [data-sonner-toast] [data-close-button]:hover { + background: rgba(255, 255, 255, 0.1) !important; +} diff --git a/libs/remix-ui/toaster/src/lib/toaster.tsx b/libs/remix-ui/toaster/src/lib/toaster.tsx index e26437d4b35..50db5370ebf 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.tsx +++ b/libs/remix-ui/toaster/src/lib/toaster.tsx @@ -3,23 +3,31 @@ import {Toaster as SonnerToaster, toast} from 'sonner' import './toaster.css' +// Export toast so callers can use toast.dismiss(id) +export {toast} + /* eslint-disable-next-line */ export interface ToasterProps { message: string | JSX.Element - timeOut?: number + timeout?: number handleHide?: () => void timestamp?: number + id?: string | number + onToastCreated?: (toastId: string | number) => void } export const Toaster = (props: ToasterProps) => { useEffect(() => { if (props.message) { // Show toast using Sonner - const duration = props.timeOut || 120000 + const duration = props.timeout || 2000 const showCloseButton = duration > 5000 + let toastId: string | number + if (typeof props.message === 'string') { - toast(props.message, { + toastId = toast(props.message, { + id: props.id, unstyled: true, duration, closeButton: showCloseButton, @@ -32,13 +40,14 @@ export const Toaster = (props: ToasterProps) => { }) } else { // For JSX elements, use toast.custom - toast.custom( + toastId = toast.custom( () => (
{props.message}
), { + id: props.id, duration, closeButton: showCloseButton, onDismiss: () => { @@ -50,6 +59,11 @@ export const Toaster = (props: ToasterProps) => { } ) } + + // Call the callback with the toast ID so caller can dismiss it later + if (props.onToastCreated) { + props.onToastCreated(toastId) + } } }, [props.message, props.timestamp]) From 9a9e9768c11242e9027af9f1cc315ef5d2e5e61d Mon Sep 17 00:00:00 2001 From: ci-bot Date: Thu, 27 Nov 2025 15:22:28 +0100 Subject: [PATCH 06/16] toast layout --- .../src/app/plugins/notification.tsx | 11 ++- .../remix-app/components/modals/dialogs.tsx | 19 +++- .../src/lib/remix-app/context/provider.tsx | 5 +- libs/remix-ui/toaster/src/lib/toaster.css | 29 +++++- libs/remix-ui/toaster/src/lib/toaster.tsx | 96 +++++++++++++++++-- 5 files changed, 136 insertions(+), 24 deletions(-) diff --git a/apps/remix-ide/src/app/plugins/notification.tsx b/apps/remix-ide/src/app/plugins/notification.tsx index a62bae72fa2..211ed1a9b9d 100644 --- a/apps/remix-ide/src/app/plugins/notification.tsx +++ b/apps/remix-ide/src/app/plugins/notification.tsx @@ -11,7 +11,7 @@ interface INotificationApi { methods: { modal: (args: AppModal) => void alert: (args: AlertModal) => void - toast: (message: string) => void + toast: (message: string) => number hideToaster: (id: number) => void } } @@ -43,12 +43,13 @@ export class NotificationPlugin extends Plugin implements MethodApi { + timestamp = timestamp || Date.now() + this.dispatcher.toast(message, timeout, timestamp) + return timestamp } async hideToaster(id: number) { - toast.dismiss(id) + toast.dismiss('toast-' + id) } } diff --git a/libs/remix-ui/app/src/lib/remix-app/components/modals/dialogs.tsx b/libs/remix-ui/app/src/lib/remix-app/components/modals/dialogs.tsx index 2731e57295b..c8e14cdf6f0 100644 --- a/libs/remix-ui/app/src/lib/remix-app/components/modals/dialogs.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/components/modals/dialogs.tsx @@ -1,16 +1,27 @@ -import React from 'react' +import React, { useMemo } from 'react' import { useDialogDispatchers, useDialogs } from '../../context/provider' -import { Toaster } from '@remix-ui/toaster' +import { ToasterContainer } from '@remix-ui/toaster' import ModalWrapper from './modal-wrapper' const AppDialogs = () => { const { handleHideModal, handleToaster } = useDialogDispatchers() - const { focusModal, focusToaster } = useDialogs() + const { focusModal, toasters } = useDialogs() + + // Map toasters to ToasterProps format with useMemo to prevent recreating on every render + const toastList = useMemo(() => { + return toasters.map((toaster) => ({ + message: toaster.message, + id: toaster.toastId || `toast-${toaster.timestamp}`, + timeout: toaster.timeout, + timestamp: toaster.timestamp, + handleHide: handleToaster + })) + }, [toasters, handleToaster]) return ( <> - + ) } diff --git a/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx b/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx index ffc9cb43c5d..3fe7ed7760d 100644 --- a/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx @@ -69,10 +69,11 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt }) } - const toast = (message: string | JSX.Element, timeout?: number) => { + const toast = (message: string | JSX.Element, timeout?: number, timestamp?: number) => { + timestamp = timestamp || Date.now() dispatch({ type: modalActionTypes.setToast, - payload: { message, timestamp: Date.now(), timeout } + payload: { message, timestamp, timeout } }) } diff --git a/libs/remix-ui/toaster/src/lib/toaster.css b/libs/remix-ui/toaster/src/lib/toaster.css index 0e33414d0a5..7ebfaed7128 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.css +++ b/libs/remix-ui/toaster/src/lib/toaster.css @@ -7,6 +7,10 @@ min-width: 350px; max-width: 600px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + word-wrap: break-word; + overflow-wrap: break-word; + white-space: normal; + line-height: 1.4; } .remixui_custom_toast { @@ -18,14 +22,30 @@ /* Override Sonner default styles to match Remix theme */ [data-sonner-toaster] { z-index: 1001 !important; + display: flex; + flex-direction: column; } [data-sonner-toast] { - background:var(--bs-primary, #212529) !important; - color: var(--bs-info-text-emphasis, #212529) !important; + background: var(--bs-light, #f8f9fa) !important; + color: var(--bs-dark, #212529) !important; border: 0px solid var(--bs-dark, #6c757d) !important; font-weight: 700 !important; font-size: 19px !important; + word-wrap: break-word; + overflow-wrap: break-word; + opacity: 1 !important; + visibility: visible !important; + pointer-events: auto !important; + position: relative; + width: 100%; + margin-bottom: 12px; + transform: none !important; +} + +[data-sonner-toast]:hover { + transform: none !important; + scale: 1 !important; } [data-sonner-toast][data-styled="true"] { @@ -50,8 +70,8 @@ right: 12px; background: transparent !important; border: none; - color: var(--bs-info-text-emphasis, #212529) !important; - opacity: 0.5; + color: var(--bs-dark, #212529) !important; + opacity: 0.7; cursor: pointer; padding: 4px; display: flex; @@ -61,6 +81,7 @@ transition: all 0.2s ease; width: 24px; height: 24px; + z-index: 1; } [data-sonner-toast] [data-close-button]:hover { diff --git a/libs/remix-ui/toaster/src/lib/toaster.tsx b/libs/remix-ui/toaster/src/lib/toaster.tsx index 50db5370ebf..75541c286a3 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.tsx +++ b/libs/remix-ui/toaster/src/lib/toaster.tsx @@ -16,6 +16,88 @@ export interface ToasterProps { onToastCreated?: (toastId: string | number) => void } +export interface ToasterContainerProps { + toasts: ToasterProps[] +} + +// Individual toast trigger component (no UI, just triggers toast) +export const ToastTrigger = (props: ToasterProps) => { + const mountedRef = React.useRef(false) + + useEffect(() => { + // Only trigger on mount, not on updates + if (!mountedRef.current && props.message && props.id) { + mountedRef.current = true + + // Show toast using Sonner - Sonner handles deduplication via ID automatically + const duration = props.timeout || 2000 + const showCloseButton = duration > 5000 + + if (typeof props.message === 'string') { + const toastId = toast(props.message, { + id: props.id, + unstyled: true, + duration, + closeButton: showCloseButton, + onDismiss: () => { + props.handleHide && props.handleHide() + }, + onAutoClose: () => { + props.handleHide && props.handleHide() + } + }) + console.log('toastId', toastId, props.id) + } else { + // For JSX elements, use toast.custom + const toastId = toast.custom( + () => ( +
+ {props.message} +
+ ), + { + id: props.id, + duration, + closeButton: showCloseButton, + onDismiss: () => { + props.handleHide && props.handleHide() + }, + onAutoClose: () => { + props.handleHide && props.handleHide() + } + } + ) + console.log('toastId', toastId, props.id) + } + } + }, []) + + return null +} + +// Container component that renders the Sonner toaster and all toast triggers +export const ToasterContainer = (props: ToasterContainerProps) => { + return ( + <> + + {props.toasts.map((toastProps) => ( + + ))} + + ) +} + +// Legacy component for backward compatibility export const Toaster = (props: ToasterProps) => { useEffect(() => { if (props.message) { @@ -26,6 +108,7 @@ export const Toaster = (props: ToasterProps) => { let toastId: string | number if (typeof props.message === 'string') { + toastId = toast(props.message, { id: props.id, unstyled: true, @@ -38,6 +121,7 @@ export const Toaster = (props: ToasterProps) => { props.handleHide && props.handleHide() } }) + console.log('toastId', toastId, props.id) } else { // For JSX elements, use toast.custom toastId = toast.custom( @@ -58,6 +142,7 @@ export const Toaster = (props: ToasterProps) => { } } ) + console.log('toastId', toastId, props.id) } // Call the callback with the toast ID so caller can dismiss it later @@ -67,15 +152,8 @@ export const Toaster = (props: ToasterProps) => { } }, [props.message, props.timestamp]) - return ( - - ) + return
} + export default Toaster From d2cf4f0fe0a39adb72922329fa5e63b72cda344c Mon Sep 17 00:00:00 2001 From: ci-bot Date: Thu, 27 Nov 2025 15:28:46 +0100 Subject: [PATCH 07/16] add loading icon --- libs/remix-ui/toaster/src/lib/toaster.tsx | 84 ++++++++++++++++------- 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/libs/remix-ui/toaster/src/lib/toaster.tsx b/libs/remix-ui/toaster/src/lib/toaster.tsx index 75541c286a3..ba5770d96e4 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.tsx +++ b/libs/remix-ui/toaster/src/lib/toaster.tsx @@ -32,26 +32,44 @@ export const ToastTrigger = (props: ToasterProps) => { // Show toast using Sonner - Sonner handles deduplication via ID automatically const duration = props.timeout || 2000 const showCloseButton = duration > 5000 + const showLoadingIcon = duration > 2000 if (typeof props.message === 'string') { - const toastId = toast(props.message, { - id: props.id, - unstyled: true, - duration, - closeButton: showCloseButton, - onDismiss: () => { - props.handleHide && props.handleHide() - }, - onAutoClose: () => { - props.handleHide && props.handleHide() + const toastId = toast.custom( + () => ( +
+ {showLoadingIcon && ( + + Loading... + + )} + {props.message} +
+ ), + { + id: props.id, + unstyled: true, + duration, + closeButton: showCloseButton, + onDismiss: () => { + props.handleHide && props.handleHide() + }, + onAutoClose: () => { + props.handleHide && props.handleHide() + } } - }) + ) console.log('toastId', toastId, props.id) } else { // For JSX elements, use toast.custom const toastId = toast.custom( () => ( -
+
+ {showLoadingIcon && ( + + Loading... + + )} {props.message}
), @@ -104,29 +122,47 @@ export const Toaster = (props: ToasterProps) => { // Show toast using Sonner const duration = props.timeout || 2000 const showCloseButton = duration > 5000 + const showLoadingIcon = duration > 2000 let toastId: string | number if (typeof props.message === 'string') { - - toastId = toast(props.message, { - id: props.id, - unstyled: true, - duration, - closeButton: showCloseButton, - onDismiss: () => { - props.handleHide && props.handleHide() - }, - onAutoClose: () => { - props.handleHide && props.handleHide() + + toastId = toast.custom( + () => ( +
+ {showLoadingIcon && ( + + Loading... + + )} + {props.message} +
+ ), + { + id: props.id, + unstyled: true, + duration, + closeButton: showCloseButton, + onDismiss: () => { + props.handleHide && props.handleHide() + }, + onAutoClose: () => { + props.handleHide && props.handleHide() + } } - }) + ) console.log('toastId', toastId, props.id) } else { // For JSX elements, use toast.custom toastId = toast.custom( () => (
+ {showLoadingIcon && ( + + Loading... + + )} {props.message}
), From b0d11355198690f05b16029a6638c64581d06b50 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Thu, 27 Nov 2025 15:36:27 +0100 Subject: [PATCH 08/16] remove log and doc --- libs/remix-ui/toaster/README.md | 147 ++++++++++++++++++++++ libs/remix-ui/toaster/src/lib/toaster.tsx | 4 - 2 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 libs/remix-ui/toaster/README.md diff --git a/libs/remix-ui/toaster/README.md b/libs/remix-ui/toaster/README.md new file mode 100644 index 00000000000..cdb3ce2729d --- /dev/null +++ b/libs/remix-ui/toaster/README.md @@ -0,0 +1,147 @@ +# Toaster Plugin Usage Guide + +This guide explains how to use the Remix Toaster notification system in your plugin. + +## Overview + +The toaster system provides a simple way to display temporary notification messages to users. It's accessible through the `notification` plugin API. + +## Basic Usage + +### Displaying a Toast + +To display a simple toast notification: + +```typescript +const id = await remix.call('notification' as any, 'toast', 'Your message here') +``` + +The `toast` method returns a unique ID (timestamp) that can be used to dismiss the toast later. + +### Displaying a Toast with Custom Timeout + +By default, toasts disappear after 2000ms (2 seconds). You can specify a custom duration: + +```typescript +// Show toast for 10 seconds +const id = await remix.call('notification' as any, 'toast', 'This message will stay longer', 10000) +``` + +**Timeout Behavior:** +- **Default:** 2000ms (2 seconds) +- **> 2000ms:** Displays a loading spinner icon +- **> 5000ms:** Displays both a loading spinner icon and a close button + +### Hiding a Toast Manually + +You can dismiss a toast before its timeout expires using the ID returned from the `toast` call: + +```typescript +const id = await remix.call('notification' as any, 'toast', 'Processing...') + +// Do some work... +await doSomeWork() + +// Hide the toast when done +await remix.call('notification' as any, 'hideToaster', id) +``` + +## API Reference + +### `toast(message: string | JSX.Element, timeout?: number, timestamp?: number): Promise` + +Displays a toast notification. + +**Parameters:** +- `message` - The message to display (string or JSX element) +- `timeout` (optional) - Duration in milliseconds before the toast auto-dismisses (default: 2000) +- `timestamp` (optional) - Custom ID for the toast (auto-generated if not provided) + +**Returns:** A unique ID (number) that can be used to dismiss the toast + +### `hideToaster(id: number): Promise` + +Manually dismisses a specific toast notification. + +**Parameters:** +- `id` - The toast ID returned by the `toast` method + +## Examples + +### Example 1: Simple Notification + +```typescript +await remix.call('notification' as any, 'toast', 'File saved successfully!') +``` + +### Example 2: Long-Running Operation + +```typescript +// Show a persistent toast with spinner +const id = await remix.call('notification' as any, 'toast', 'Compiling contracts...', 30000) + +try { + await compileContracts() + // Hide the toast when done + await remix.call('notification' as any, 'hideToaster', id) + // Show success message + await remix.call('notification' as any, 'toast', 'Compilation completed!') +} catch (error) { + await remix.call('notification' as any, 'hideToaster', id) + await remix.call('notification' as any, 'toast', 'Compilation failed!') +} +``` + +### Example 3: Multiple Sequential Toasts + +```typescript +const id1 = await remix.call('notification' as any, 'toast', 'Step 1: Initializing...') +await step1() + +const id2 = await remix.call('notification' as any, 'toast', 'Step 2: Processing...') +await step2() + +const id3 = await remix.call('notification' as any, 'toast', 'Step 3: Finalizing...') +await step3() + +await remix.call('notification' as any, 'toast', 'All steps completed!') +``` + +## Best Practices + +1. **Keep messages concise** - Toast notifications should be brief and to the point +2. **Use appropriate timeouts** - Short messages (< 10 words) can use the default timeout, longer messages should have extended timeouts +3. **Clean up long-running toasts** - Always hide toasts for long-running operations once they complete +4. **Provide feedback** - Use toasts to confirm user actions (saves, deletions, etc.) +5. **Don't overuse** - Too many toasts can be overwhelming; use them for important notifications only + +## UI Features + +- **Position:** Top-right corner of the screen +- **Styling:** Uses Bootstrap alert classes (`alert-info`) +- **Loading indicator:** Automatically shown for toasts with timeout > 2000ms +- **Close button:** Automatically shown for toasts with timeout > 5000ms +- **Auto-dismiss:** Toasts automatically disappear after the specified timeout +- **Manual dismiss:** Toasts can be dismissed early using `hideToaster` + +## TypeScript Types + +```typescript +interface ToasterProps { + message: string | JSX.Element + timeout?: number + handleHide?: () => void + timestamp?: number + id?: string | number + onToastCreated?: (toastId: string | number) => void +} +``` + +## Related APIs + +The notification plugin also provides other methods for user interaction: + +- `modal()` - Display a modal dialog +- `alert()` - Display an alert dialog + +For more information, see the notification plugin documentation. diff --git a/libs/remix-ui/toaster/src/lib/toaster.tsx b/libs/remix-ui/toaster/src/lib/toaster.tsx index ba5770d96e4..654105b791e 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.tsx +++ b/libs/remix-ui/toaster/src/lib/toaster.tsx @@ -59,7 +59,6 @@ export const ToastTrigger = (props: ToasterProps) => { } } ) - console.log('toastId', toastId, props.id) } else { // For JSX elements, use toast.custom const toastId = toast.custom( @@ -85,7 +84,6 @@ export const ToastTrigger = (props: ToasterProps) => { } } ) - console.log('toastId', toastId, props.id) } } }, []) @@ -152,7 +150,6 @@ export const Toaster = (props: ToasterProps) => { } } ) - console.log('toastId', toastId, props.id) } else { // For JSX elements, use toast.custom toastId = toast.custom( @@ -178,7 +175,6 @@ export const Toaster = (props: ToasterProps) => { } } ) - console.log('toastId', toastId, props.id) } // Call the callback with the toast ID so caller can dismiss it later From 70a7020e6b8ad74fa2c57272dbd26b0986f8aa77 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Tue, 2 Dec 2025 16:12:48 +0100 Subject: [PATCH 09/16] fix stying --- .../src/app/plugins/notification.tsx | 1 + libs/remix-ui/toaster/src/lib/toaster.css | 41 +++++++++++++++++-- libs/remix-ui/toaster/src/lib/toaster.tsx | 34 ++++++++++++--- 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/apps/remix-ide/src/app/plugins/notification.tsx b/apps/remix-ide/src/app/plugins/notification.tsx index 211ed1a9b9d..76074b9bc00 100644 --- a/apps/remix-ide/src/app/plugins/notification.tsx +++ b/apps/remix-ide/src/app/plugins/notification.tsx @@ -45,6 +45,7 @@ export class NotificationPlugin extends Plugin implements MethodApi { timestamp = timestamp || Date.now() + timestamp = timestamp + ++this.toastId this.dispatcher.toast(message, timeout, timestamp) return timestamp } diff --git a/libs/remix-ui/toaster/src/lib/toaster.css b/libs/remix-ui/toaster/src/lib/toaster.css index 7ebfaed7128..3a4874bc8d9 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.css +++ b/libs/remix-ui/toaster/src/lib/toaster.css @@ -1,7 +1,7 @@ /* Sonner toast styling */ .remixui_sonner_toast { border-radius: 8px; - padding: 20px 28px; + padding: 20px 48px 20px 28px; font-weight: 700 !important; font-size: 19px !important; min-width: 350px; @@ -45,7 +45,6 @@ [data-sonner-toast]:hover { transform: none !important; - scale: 1 !important; } [data-sonner-toast][data-styled="true"] { @@ -87,7 +86,6 @@ [data-sonner-toast] [data-close-button]:hover { opacity: 1; background: rgba(0, 0, 0, 0.05) !important; - transform: scale(1.1); } [data-sonner-toast] [data-close-button] svg { @@ -105,3 +103,40 @@ .dark [data-sonner-toast] [data-close-button]:hover { background: rgba(255, 255, 255, 0.1) !important; } + +/* Custom codicon close button styling */ +[data-sonner-toast] .codicon-close { + position: absolute; + top: 12px; + right: 12px; + background: transparent !important; + border: none; + color: var(--bs-dark, #212529) !important; + opacity: 0.7; + cursor: pointer; + padding: 4px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: all 0.2s ease; + width: 24px; + height: 24px; + z-index: 1; +} + +[data-sonner-toast] .codicon-close:hover { + opacity: 1; + background: rgba(0, 0, 0, 0.05) !important; +} + +/* Dark theme codicon close button */ +[data-sonner-toast][data-theme="dark"] .codicon-close, +.dark [data-sonner-toast] .codicon-close { + color: var(--bs-light, #f8f9fa) !important; +} + +[data-sonner-toast][data-theme="dark"] .codicon-close:hover, +.dark [data-sonner-toast] .codicon-close:hover { + background: rgba(255, 255, 255, 0.1) !important; +} diff --git a/libs/remix-ui/toaster/src/lib/toaster.tsx b/libs/remix-ui/toaster/src/lib/toaster.tsx index 654105b791e..2191d55b490 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.tsx +++ b/libs/remix-ui/toaster/src/lib/toaster.tsx @@ -31,7 +31,7 @@ export const ToastTrigger = (props: ToasterProps) => { // Show toast using Sonner - Sonner handles deduplication via ID automatically const duration = props.timeout || 2000 - const showCloseButton = duration > 5000 + const showCloseButton = duration > 2000 const showLoadingIcon = duration > 2000 if (typeof props.message === 'string') { @@ -39,10 +39,14 @@ export const ToastTrigger = (props: ToasterProps) => { () => (
{showLoadingIcon && ( - + Loading... )} + {showCloseButton && ( + toast.dismiss(toastId)} role="status"> + + )} {props.message}
), @@ -59,16 +63,24 @@ export const ToastTrigger = (props: ToasterProps) => { } } ) + // Call the callback with the toast ID so caller can dismiss it later + if (props.onToastCreated) { + props.onToastCreated(toastId) + } } else { // For JSX elements, use toast.custom const toastId = toast.custom( () => (
{showLoadingIcon && ( - + Loading... )} + {showCloseButton && ( + toast.dismiss(toastId)} role="status"> + + )} {props.message}
), @@ -84,6 +96,10 @@ export const ToastTrigger = (props: ToasterProps) => { } } ) + // Call the callback with the toast ID so caller can dismiss it later + if (props.onToastCreated) { + props.onToastCreated(toastId) + } } } }, []) @@ -130,10 +146,14 @@ export const Toaster = (props: ToasterProps) => { () => (
{showLoadingIcon && ( - + Loading... )} + {showCloseButton && ( + toast.dismiss(toastId)} role="status"> + + )} {props.message}
), @@ -156,10 +176,14 @@ export const Toaster = (props: ToasterProps) => { () => (
{showLoadingIcon && ( - + Loading... )} + {showCloseButton && ( + toast.dismiss(toastId)} role="status"> + + )} {props.message}
), From 7c1c4843fbd3237aff7fc102b6b30cd0a66a2c68 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Wed, 3 Dec 2025 09:37:49 +0100 Subject: [PATCH 10/16] use bootstrap 5 --- libs/remix-ui/toaster/src/lib/toaster.css | 74 ++++++++++++- libs/remix-ui/toaster/src/lib/toaster.tsx | 128 +++++++++++++--------- 2 files changed, 144 insertions(+), 58 deletions(-) diff --git a/libs/remix-ui/toaster/src/lib/toaster.css b/libs/remix-ui/toaster/src/lib/toaster.css index 3a4874bc8d9..24293af2c3d 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.css +++ b/libs/remix-ui/toaster/src/lib/toaster.css @@ -1,12 +1,11 @@ /* Sonner toast styling */ .remixui_sonner_toast { border-radius: 8px; - padding: 20px 48px 20px 28px; + padding: 5px 5px 5px 5px; font-weight: 700 !important; font-size: 19px !important; min-width: 350px; max-width: 600px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); word-wrap: break-word; overflow-wrap: break-word; white-space: normal; @@ -24,10 +23,30 @@ z-index: 1001 !important; display: flex; flex-direction: column; + transform: none !important; + --normal-bg: transparent !important; + --normal-border: transparent !important; +} + +[data-sonner-toaster]:hover { + transform: none !important; +} + +[data-sonner-toaster]:hover [data-sonner-toast] { + transform: none !important; + scale: 1 !important; + opacity: 1 !important; +} + +[data-sonner-toaster][data-y-position="top"] { + top: 0; +} + +[data-sonner-toaster][data-x-position="right"] { + right: 0; } [data-sonner-toast] { - background: var(--bs-light, #f8f9fa) !important; color: var(--bs-dark, #212529) !important; border: 0px solid var(--bs-dark, #6c757d) !important; font-weight: 700 !important; @@ -37,20 +56,56 @@ opacity: 1 !important; visibility: visible !important; pointer-events: auto !important; - position: relative; + position: relative !important; width: 100%; - margin-bottom: 12px; transform: none !important; + transition: none !important; + animation: none !important; } [data-sonner-toast]:hover { transform: none !important; + scale: 1 !important; + transition: none !important; +} + +[data-sonner-toast][data-expanded="true"] { + transform: none !important; + scale: 1 !important; + transition: none !important; +} + +[data-sonner-toast][data-expanded="false"] { + transform: none !important; + scale: 1 !important; + transition: none !important; +} + +[data-sonner-toast][data-front="true"] { + transform: none !important; + scale: 1 !important; + transition: none !important; +} + +[data-sonner-toast][data-front="false"] { + transform: none !important; + scale: 1 !important; + opacity: 1 !important; + transition: none !important; +} + +[data-sonner-toast][data-index]:not([data-index="0"]) { + transform: none !important; + scale: 1 !important; + opacity: 1 !important; + transition: none !important; } [data-sonner-toast][data-styled="true"] { padding: 20px 28px !important; gap: 12px !important; font-weight: 700 !important; + transform: none !important; } /* Dark theme support */ @@ -60,6 +115,7 @@ color: var(--bs-light, #f8f9fa) !important; border: 0px solid var(--bs-secondary, #6c757d) !important; font-weight: 700 !important; + transform: none !important; } /* Close button styling */ @@ -140,3 +196,11 @@ .dark [data-sonner-toast] .codicon-close:hover { background: rgba(255, 255, 255, 0.1) !important; } + +[data-sonner-toast][data-expanded=false][data-front=false] { + height: auto !important; +} + +[data-sonner-toast][data-mounted=true][data-expanded=true] { + height: auto !important; +} \ No newline at end of file diff --git a/libs/remix-ui/toaster/src/lib/toaster.tsx b/libs/remix-ui/toaster/src/lib/toaster.tsx index 2191d55b490..c483603603d 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.tsx +++ b/libs/remix-ui/toaster/src/lib/toaster.tsx @@ -31,30 +31,34 @@ export const ToastTrigger = (props: ToasterProps) => { // Show toast using Sonner - Sonner handles deduplication via ID automatically const duration = props.timeout || 2000 - const showCloseButton = duration > 2000 + const showCloseButton = true const showLoadingIcon = duration > 2000 if (typeof props.message === 'string') { const toastId = toast.custom( () => ( -
- {showLoadingIcon && ( - - Loading... - - )} - {showCloseButton && ( - toast.dismiss(toastId)} role="status"> - - )} - {props.message} +
+
+ {showLoadingIcon && ( + + Loading... + + )} + Remix + {showCloseButton && ( + + )} +
+
+ {props.message} +
), { id: props.id, unstyled: true, duration, - closeButton: showCloseButton, + closeButton: false, onDismiss: () => { props.handleHide && props.handleHide() }, @@ -71,23 +75,27 @@ export const ToastTrigger = (props: ToasterProps) => { // For JSX elements, use toast.custom const toastId = toast.custom( () => ( -
- {showLoadingIcon && ( - - Loading... - - )} - {showCloseButton && ( - toast.dismiss(toastId)} role="status"> - - )} - {props.message} +
+
+ {showLoadingIcon && ( + + Loading... + + )} + Remix + {showCloseButton && ( + + )} +
+
+ {props.message} +
), { id: props.id, duration, - closeButton: showCloseButton, + closeButton: false, onDismiss: () => { props.handleHide && props.handleHide() }, @@ -113,10 +121,16 @@ export const ToasterContainer = (props: ToasterContainerProps) => { <> {props.toasts.map((toastProps) => ( @@ -135,7 +149,7 @@ export const Toaster = (props: ToasterProps) => { if (props.message) { // Show toast using Sonner const duration = props.timeout || 2000 - const showCloseButton = duration > 5000 + const showCloseButton = true const showLoadingIcon = duration > 2000 let toastId: string | number @@ -144,24 +158,28 @@ export const Toaster = (props: ToasterProps) => { toastId = toast.custom( () => ( -
- {showLoadingIcon && ( - - Loading... - - )} - {showCloseButton && ( - toast.dismiss(toastId)} role="status"> - - )} - {props.message} +
+
+ {showLoadingIcon && ( + + Loading... + + )} + Remix + {showCloseButton && ( + + )} +
+
+ {props.message} +
), { id: props.id, unstyled: true, duration, - closeButton: showCloseButton, + closeButton: false, onDismiss: () => { props.handleHide && props.handleHide() }, @@ -174,23 +192,27 @@ export const Toaster = (props: ToasterProps) => { // For JSX elements, use toast.custom toastId = toast.custom( () => ( -
- {showLoadingIcon && ( - - Loading... - - )} - {showCloseButton && ( - toast.dismiss(toastId)} role="status"> - - )} - {props.message} +
+
+ {showLoadingIcon && ( + + Loading... + + )} + Remix + {showCloseButton && ( + + )} +
+
+ {props.message} +
), { id: props.id, duration, - closeButton: showCloseButton, + closeButton: false, onDismiss: () => { props.handleHide && props.handleHide() }, From 1ae4c99a79cc2e17461fc434e84591052c11cc83 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Wed, 3 Dec 2025 21:40:06 +0530 Subject: [PATCH 11/16] updated toaster classes --- apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css | 6 +++--- apps/remix-ide/src/assets/css/themes/remix-light_powaqg.css | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css b/apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css index bda4bbc9a6e..2a5dfb2ac57 100644 --- a/apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css +++ b/apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css @@ -5596,14 +5596,14 @@ textarea.form-control-lg { --bs-toast-spacing: 1.5rem; --bs-toast-max-width: 350px; --bs-toast-font-size: 0.875rem; - --bs-toast-color: ; - --bs-toast-bg: #444; + --bs-toast-color: var(--bs-body-color); + --bs-toast-bg: var(--bs-light); --bs-toast-border-width: var(--bs-border-width); --bs-toast-border-color: var(--bs-border-color-translucent); --bs-toast-border-radius: var(--bs-border-radius); --bs-toast-box-shadow: var(--bs-box-shadow); --bs-toast-header-color: var(--bs-secondary-color); - --bs-toast-header-bg: #303030; + --bs-toast-header-bg: var(--bs-body-bg); --bs-toast-header-border-color: var(--bs-border-color-translucent); width: var(--bs-toast-max-width); max-width: 100%; diff --git a/apps/remix-ide/src/assets/css/themes/remix-light_powaqg.css b/apps/remix-ide/src/assets/css/themes/remix-light_powaqg.css index c7c87781995..7e6201eb42d 100644 --- a/apps/remix-ide/src/assets/css/themes/remix-light_powaqg.css +++ b/apps/remix-ide/src/assets/css/themes/remix-light_powaqg.css @@ -5598,14 +5598,14 @@ textarea.form-control-lg { --bs-toast-spacing: 1.5rem; --bs-toast-max-width: 350px; --bs-toast-font-size: 0.875rem; - --bs-toast-color: ; - --bs-toast-bg: rgba(var(--bs-body-bg-rgb), 0.85); + --bs-toast-color: var(--bs-body-color); + --bs-toast-bg: var(--bs-light); --bs-toast-border-width: var(--bs-border-width); --bs-toast-border-color: var(--bs-border-color-translucent); --bs-toast-border-radius: var(--bs-border-radius); --bs-toast-box-shadow: var(--bs-box-shadow); --bs-toast-header-color: var(--bs-secondary-color); - --bs-toast-header-bg: rgba(var(--bs-body-bg-rgb), 0.85); + --bs-toast-header-bg: var(--bs-body-bg); --bs-toast-header-border-color: var(--bs-border-color-translucent); width: var(--bs-toast-max-width); max-width: 100%; From bca8ea37a309f5fa9e97261098f20563f069a7a2 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 4 Dec 2025 13:43:55 +0100 Subject: [PATCH 12/16] Fix indentation in toast method definition --- apps/remix-ide/src/app/plugins/notification.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/remix-ide/src/app/plugins/notification.tsx b/apps/remix-ide/src/app/plugins/notification.tsx index 76074b9bc00..d099fe578dd 100644 --- a/apps/remix-ide/src/app/plugins/notification.tsx +++ b/apps/remix-ide/src/app/plugins/notification.tsx @@ -43,7 +43,7 @@ export class NotificationPlugin extends Plugin implements MethodApi { + async toast(message: string | JSX.Element, timeout?: number, timestamp?: number): Promise { timestamp = timestamp || Date.now() timestamp = timestamp + ++this.toastId this.dispatcher.toast(message, timeout, timestamp) From a9f80c5cd3150be680083b430a8707f624374d7e Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 4 Dec 2025 13:55:12 +0100 Subject: [PATCH 13/16] Fix formatting and spacing in toaster.tsx --- libs/remix-ui/toaster/src/lib/toaster.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/libs/remix-ui/toaster/src/lib/toaster.tsx b/libs/remix-ui/toaster/src/lib/toaster.tsx index c483603603d..ad940951028 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.tsx +++ b/libs/remix-ui/toaster/src/lib/toaster.tsx @@ -1,10 +1,10 @@ import React, {useEffect} from 'react' // eslint-disable-line -import {Toaster as SonnerToaster, toast} from 'sonner' +import { Toaster as SonnerToaster, toast } from 'sonner' import './toaster.css' // Export toast so callers can use toast.dismiss(id) -export {toast} +export { toast } /* eslint-disable-next-line */ export interface ToasterProps { @@ -105,9 +105,9 @@ export const ToastTrigger = (props: ToasterProps) => { } ) // Call the callback with the toast ID so caller can dismiss it later - if (props.onToastCreated) { - props.onToastCreated(toastId) - } + if (props.onToastCreated) { + props.onToastCreated(toastId) + } } } }, []) @@ -230,8 +230,7 @@ export const Toaster = (props: ToasterProps) => { } }, [props.message, props.timestamp]) - return
+ return
} - export default Toaster From a7a5fdad65dc9923179b6d5d849f1afd77cba905 Mon Sep 17 00:00:00 2001 From: yann300 Date: Fri, 5 Dec 2025 10:27:33 +0100 Subject: [PATCH 14/16] fix test --- libs/remix-ui/toaster/src/lib/toaster.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/remix-ui/toaster/src/lib/toaster.tsx b/libs/remix-ui/toaster/src/lib/toaster.tsx index ad940951028..6440b61f069 100644 --- a/libs/remix-ui/toaster/src/lib/toaster.tsx +++ b/libs/remix-ui/toaster/src/lib/toaster.tsx @@ -37,7 +37,7 @@ export const ToastTrigger = (props: ToasterProps) => { if (typeof props.message === 'string') { const toastId = toast.custom( () => ( -
+
{showLoadingIcon && ( @@ -75,7 +75,7 @@ export const ToastTrigger = (props: ToasterProps) => { // For JSX elements, use toast.custom const toastId = toast.custom( () => ( -
+
{showLoadingIcon && ( @@ -158,7 +158,7 @@ export const Toaster = (props: ToasterProps) => { toastId = toast.custom( () => ( -
+
{showLoadingIcon && ( @@ -192,7 +192,7 @@ export const Toaster = (props: ToasterProps) => { // For JSX elements, use toast.custom toastId = toast.custom( () => ( -
+
{showLoadingIcon && ( From b056bc76d480bcdb4bd5fc23d04d5b987b4a62e5 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Mon, 8 Dec 2025 10:42:18 +0100 Subject: [PATCH 15/16] fix test --- .../commands/waitForElementContainsText.ts | 64 +++++++++++++++++-- apps/remix-ide-e2e/src/tests/url.test.ts | 4 +- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/apps/remix-ide-e2e/src/commands/waitForElementContainsText.ts b/apps/remix-ide-e2e/src/commands/waitForElementContainsText.ts index 6ce01beaa01..c8076c7939f 100644 --- a/apps/remix-ide-e2e/src/commands/waitForElementContainsText.ts +++ b/apps/remix-ide-e2e/src/commands/waitForElementContainsText.ts @@ -1,25 +1,75 @@ import { NightwatchBrowser } from 'nightwatch' import EventEmitter from 'events' +const findElementsAsync = (browser: NightwatchBrowser, selector: string): Promise => { + return new Promise((resolve, reject) => { + browser.findElements(selector, (result) => { + resolve(result.value as any) + }) + }) +} + +const getTextAsync = (browser: NightwatchBrowser, elementId: string): Promise => { + return new Promise((resolve, reject) => { + browser.getText(elementId, (result) => { + const text = typeof result === 'string' ? result : result.value + resolve(text as any) + }) + }) +} + + class WaitForElementContainsText extends EventEmitter { command (this: NightwatchBrowser, id: string, value: string, timeout = 10000): NightwatchBrowser { let waitId // eslint-disable-line - let currentValue - const runid = setInterval(() => { - this.api.getText(id, (result) => { - currentValue = result.value - if (typeof result.value === 'string' && result.value.indexOf(value) !== -1) { + let currentValues: string[] = [] + const runid = setInterval(async () => { + try { + + let elements = await findElementsAsync(this.api, id) + + if (!elements) { + currentValues = [] + return + } + + if (elements.length === 0) { + currentValues = [] + return + } + + // Check all elements that match the selector + let foundMatch = false + const textValues: string[] = [] + + for (const element of elements) { + let text = await getTextAsync(this.api, element) + currentValues.push(text) + + if (typeof text === 'string' && text.indexOf(value) !== -1) { + foundMatch = true + break + } + } + + currentValues = textValues + + if (foundMatch) { clearInterval(runid) clearTimeout(waitId) this.api.assert.ok(true, `WaitForElementContainsText ${id} contains ${value}`) this.emit('complete') } - }) + } catch (err) { + // Ignore errors and continue polling + console.error(`Error in waitForElementContainsText for selector ${id}:`, err) + } }, 200) waitId = setTimeout(() => { clearInterval(runid) - this.api.assert.fail(`TimeoutError: An error occurred while running .waitForElementContainsText() command on ${id} after ${timeout} milliseconds. expected: ${value} - got: ${currentValue}`) + const valuesFound = currentValues.length > 0 ? currentValues.join(', ') : 'none' + this.api.assert.fail(`TimeoutError: An error occurred while running .waitForElementContainsText() command on ${id} after ${timeout} milliseconds. expected: ${value} - got: ${valuesFound}`) }, timeout) return this } diff --git a/apps/remix-ide-e2e/src/tests/url.test.ts b/apps/remix-ide-e2e/src/tests/url.test.ts index 8e00f424e29..d70c94cfb64 100644 --- a/apps/remix-ide-e2e/src/tests/url.test.ts +++ b/apps/remix-ide-e2e/src/tests/url.test.ts @@ -351,7 +351,7 @@ module.exports = { locateStrategy: 'xpath' }) }, - +/* 'Should load using compiler from link passed in remix URL #group3': function (browser: NightwatchBrowser) { browser .url('http://127.0.0.1:8080/#version=https://solidity-blog.s3.eu-central-1.amazonaws.com/data/08preview/soljson.js&optimize=false') @@ -383,7 +383,7 @@ module.exports = { .openFile('contracts/governance') .openFile('contracts/governance/UnionGovernor.sol') }, - +*/ 'Should execute function call from URL parameters #group3': function (browser: NightwatchBrowser) { browser .switchWorkspace('default_workspace') From 7fffa2f197fa5acdc879669acd93bfc4481d0678 Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 8 Dec 2025 17:04:51 +0100 Subject: [PATCH 16/16] Remove commented test for compiler URL loading Removed commented-out test case for loading compiler from URL. --- apps/remix-ide-e2e/src/tests/url.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/url.test.ts b/apps/remix-ide-e2e/src/tests/url.test.ts index d70c94cfb64..8e00f424e29 100644 --- a/apps/remix-ide-e2e/src/tests/url.test.ts +++ b/apps/remix-ide-e2e/src/tests/url.test.ts @@ -351,7 +351,7 @@ module.exports = { locateStrategy: 'xpath' }) }, -/* + 'Should load using compiler from link passed in remix URL #group3': function (browser: NightwatchBrowser) { browser .url('http://127.0.0.1:8080/#version=https://solidity-blog.s3.eu-central-1.amazonaws.com/data/08preview/soljson.js&optimize=false') @@ -383,7 +383,7 @@ module.exports = { .openFile('contracts/governance') .openFile('contracts/governance/UnionGovernor.sol') }, -*/ + 'Should execute function call from URL parameters #group3': function (browser: NightwatchBrowser) { browser .switchWorkspace('default_workspace')