From 405bf707a398c82529c6c72470f11fd6a744064b Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Wed, 29 Jan 2025 13:25:25 -0800 Subject: [PATCH 01/19] Desktop: Make trash notification more accessible --- .eslintignore | 4 + .gitignore | 4 + .../PopupNotification/NotificationItem.tsx | 42 +++++++ .../PopupNotificationProvider.tsx | 102 +++++++++++++++ .../gui/PopupNotification/types.ts | 16 +++ packages/app-desktop/gui/Root.tsx | 17 +-- .../TrashNotification/TrashNotification.tsx | 82 +++++------- .../TrashNotificationMessage.tsx | 27 ++++ packages/app-desktop/gui/styles/index.scss | 3 + .../app-desktop/gui/styles/link-button.scss | 13 ++ .../gui/styles/popup-notification-item.scss | 117 ++++++++++++++++++ .../gui/styles/popup-notification-list.scss | 14 +++ 12 files changed, 381 insertions(+), 60 deletions(-) create mode 100644 packages/app-desktop/gui/PopupNotification/NotificationItem.tsx create mode 100644 packages/app-desktop/gui/PopupNotification/PopupNotificationProvider.tsx create mode 100644 packages/app-desktop/gui/PopupNotification/types.ts create mode 100644 packages/app-desktop/gui/TrashNotification/TrashNotificationMessage.tsx create mode 100644 packages/app-desktop/gui/styles/link-button.scss create mode 100644 packages/app-desktop/gui/styles/popup-notification-item.scss create mode 100644 packages/app-desktop/gui/styles/popup-notification-list.scss diff --git a/.eslintignore b/.eslintignore index a93e5f4a19e..52cecf708c4 100644 --- a/.eslintignore +++ b/.eslintignore @@ -358,6 +358,9 @@ packages/app-desktop/gui/PasswordInput/PasswordInput.js packages/app-desktop/gui/PasswordInput/types.js packages/app-desktop/gui/PdfViewer.js packages/app-desktop/gui/PluginNotification/PluginNotification.js +packages/app-desktop/gui/PopupNotification/NotificationItem.js +packages/app-desktop/gui/PopupNotification/PopupNotificationProvider.js +packages/app-desktop/gui/PopupNotification/types.js packages/app-desktop/gui/PromptDialog.js packages/app-desktop/gui/ResizableLayout/LayoutItemContainer.js packages/app-desktop/gui/ResizableLayout/MoveButtons.js @@ -419,6 +422,7 @@ packages/app-desktop/gui/ToolbarBase.js packages/app-desktop/gui/ToolbarButton/ToolbarButton.js packages/app-desktop/gui/ToolbarSpace.js packages/app-desktop/gui/TrashNotification/TrashNotification.js +packages/app-desktop/gui/TrashNotification/TrashNotificationMessage.js packages/app-desktop/gui/UpdateNotification/UpdateNotification.js packages/app-desktop/gui/WindowCommandsAndDialogs/AppDialogs.js packages/app-desktop/gui/WindowCommandsAndDialogs/ModalMessageOverlay.js diff --git a/.gitignore b/.gitignore index 950b8822c58..736bc584b3f 100644 --- a/.gitignore +++ b/.gitignore @@ -333,6 +333,9 @@ packages/app-desktop/gui/PasswordInput/PasswordInput.js packages/app-desktop/gui/PasswordInput/types.js packages/app-desktop/gui/PdfViewer.js packages/app-desktop/gui/PluginNotification/PluginNotification.js +packages/app-desktop/gui/PopupNotification/NotificationItem.js +packages/app-desktop/gui/PopupNotification/PopupNotificationProvider.js +packages/app-desktop/gui/PopupNotification/types.js packages/app-desktop/gui/PromptDialog.js packages/app-desktop/gui/ResizableLayout/LayoutItemContainer.js packages/app-desktop/gui/ResizableLayout/MoveButtons.js @@ -394,6 +397,7 @@ packages/app-desktop/gui/ToolbarBase.js packages/app-desktop/gui/ToolbarButton/ToolbarButton.js packages/app-desktop/gui/ToolbarSpace.js packages/app-desktop/gui/TrashNotification/TrashNotification.js +packages/app-desktop/gui/TrashNotification/TrashNotificationMessage.js packages/app-desktop/gui/UpdateNotification/UpdateNotification.js packages/app-desktop/gui/WindowCommandsAndDialogs/AppDialogs.js packages/app-desktop/gui/WindowCommandsAndDialogs/ModalMessageOverlay.js diff --git a/packages/app-desktop/gui/PopupNotification/NotificationItem.tsx b/packages/app-desktop/gui/PopupNotification/NotificationItem.tsx new file mode 100644 index 00000000000..61aa6ee7f67 --- /dev/null +++ b/packages/app-desktop/gui/PopupNotification/NotificationItem.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; +import { NotificationType } from './types'; + +export interface NotificationDismissEvent { + key: string; +} + +interface Props { + children: React.ReactNode; + key: string; + type: NotificationType; + onDismiss: (event: NotificationDismissEvent)=> void; + dismissing: boolean; +} + +const NotificationItem: React.FC = props => { + const iconClassName = (() => { + if (props.type === NotificationType.Success) return 'fas fa-check'; + if (props.type === NotificationType.Error) return 'fas fa-times'; + if (props.type === NotificationType.Info) return 'fas fa-info'; + return ''; + })(); + + const containerModifier = (() => { + if (props.type === NotificationType.Success) return '-success'; + if (props.type === NotificationType.Error) return '-error'; + return ''; + })(); + + return
+ +
+
+ {props.children} +
+
; +}; + +export default NotificationItem; diff --git a/packages/app-desktop/gui/PopupNotification/PopupNotificationProvider.tsx b/packages/app-desktop/gui/PopupNotification/PopupNotificationProvider.tsx new file mode 100644 index 00000000000..6ecbf17277a --- /dev/null +++ b/packages/app-desktop/gui/PopupNotification/PopupNotificationProvider.tsx @@ -0,0 +1,102 @@ +import * as React from 'react'; +import { createContext, useCallback, useMemo, useRef, useState } from 'react'; +import { NotificationType, PopupHandle, PopupControl as PopupManager } from './types'; +import NotificationItem, { NotificationDismissEvent } from './NotificationItem'; + +export const PopupNotificationContext = createContext(null); + +interface Props { + children: React.ReactNode; +} + +interface PopupSpec { + key: string; + dismissing: boolean; + type: NotificationType; + content: ()=> React.ReactNode; +} + +const PopupNotificationProvider: React.FC = props => { + const [popupSpecs, setPopupSpecs] = useState([]); + const nextPopupKey = useRef(0); + + const onNotificationDismiss = useCallback((event: NotificationDismissEvent) => { + // Start the dismiss animation + setPopupSpecs(popups => popups.map(p => { + if (p.key === event.key) { + return { ...p, dismissing: true }; + } else { + return p; + } + })); + + // Remove the popup + const dismissAnimationDelay = 500; + setTimeout(() => { + setPopupSpecs(popups => popups.filter(p => p.key !== event.key)); + }, dismissAnimationDelay); + }, []); + + const popupManager = useMemo((): PopupManager => { + return { + createPopup(content, type: NotificationType): PopupHandle { + const key = `popup-${nextPopupKey.current++}`; + const newPopup = { + key, + content, + type, + dismissing: false, + }; + + setPopupSpecs(popups => { + const newPopups = [...popups]; + + // Replace the existing popup, if it exists + const insertIndex = newPopups.findIndex(p => p.key === key); + if (insertIndex === -1) { + newPopups.push(newPopup); + } else { + newPopups.splice(insertIndex, 1, newPopup); + } + + return newPopups; + }); + + return { + remove() { + onNotificationDismiss({ key }); + }, + // Default to removing after 5.5s + 0.5s + // See https://www.sheribyrnehaber.com/designing-toast-messages-for-accessibility/ + scheduleRemove(delay = 5_500) { + setTimeout(() => { + this.remove(); + }, delay); + }, + }; + }, + }; + }, [onNotificationDismiss]); + + const popups = []; + for (const spec of popupSpecs) { + popups.push( + {spec.content()}, + ); + } + popups.reverse(); + + return + {props.children} +
+ {popups} +
+
; +}; + +export default PopupNotificationProvider; diff --git a/packages/app-desktop/gui/PopupNotification/types.ts b/packages/app-desktop/gui/PopupNotification/types.ts new file mode 100644 index 00000000000..6483b11c64a --- /dev/null +++ b/packages/app-desktop/gui/PopupNotification/types.ts @@ -0,0 +1,16 @@ +import * as React from 'react'; + +export type PopupHandle = { + remove(): void; + scheduleRemove(delay?: number): void; +}; + +export enum NotificationType { + Info = 'info', + Success = 'success', + Error = 'error', +} + +export interface PopupControl { + createPopup(content: ()=> React.ReactNode, type: NotificationType): PopupHandle; +} diff --git a/packages/app-desktop/gui/Root.tsx b/packages/app-desktop/gui/Root.tsx index 25f39c90782..9fe3ccd9cb4 100644 --- a/packages/app-desktop/gui/Root.tsx +++ b/packages/app-desktop/gui/Root.tsx @@ -30,6 +30,7 @@ import WindowCommandsAndDialogs from './WindowCommandsAndDialogs/WindowCommandsA import { defaultWindowId, stateUtils, WindowState } from '@joplin/lib/reducer'; import bridge from '../services/bridge'; import EditorWindow from './NoteEditor/EditorWindow'; +import PopupNotificationProvider from './PopupNotification/PopupNotificationProvider'; const { ThemeProvider, StyleSheetManager, createGlobalStyle } = require('styled-components'); interface Props { @@ -197,13 +198,15 @@ class RootComponent extends React.Component { return ( - - - - - - {this.renderSecondaryWindows()} - {this.renderModalMessage(this.modalDialogProps())} + + + + + + + {this.renderSecondaryWindows()} + {this.renderModalMessage(this.modalDialogProps())} + ); diff --git a/packages/app-desktop/gui/TrashNotification/TrashNotification.tsx b/packages/app-desktop/gui/TrashNotification/TrashNotification.tsx index 951e9965a41..532b2f09005 100644 --- a/packages/app-desktop/gui/TrashNotification/TrashNotification.tsx +++ b/packages/app-desktop/gui/TrashNotification/TrashNotification.tsx @@ -1,15 +1,13 @@ -import { useContext, useCallback, useMemo, useRef } from 'react'; +import * as React from 'react'; +import { useContext, useEffect, useRef } from 'react'; import { StateLastDeletion } from '@joplin/lib/reducer'; import { _, _n } from '@joplin/lib/locale'; -import NotyfContext from '../NotyfContext'; -import { waitForElement } from '@joplin/lib/dom'; -import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect'; -import { htmlentities } from '@joplin/utils/html'; import restoreItems from '@joplin/lib/services/trash/restoreItems'; import { ModelType } from '@joplin/lib/BaseModel'; -import { themeStyle } from '@joplin/lib/theme'; import { Dispatch } from 'redux'; -import { NotyfNotification } from 'notyf'; +import { PopupNotificationContext } from '../PopupNotification/PopupNotificationProvider'; +import { NotificationType } from '../PopupNotification/types'; +import TrashNotificationMessage from './TrashNotificationMessage'; interface Props { lastDeletion: StateLastDeletion; @@ -18,50 +16,29 @@ interface Props { dispatch: Dispatch; } -export default (props: Props) => { - const notyfContext = useContext(NotyfContext); - const notificationRef = useRef(null); - - const theme = useMemo(() => { - return themeStyle(props.themeId); - }, [props.themeId]); - - const notyf = useMemo(() => { - const output = notyfContext; - output.options.types = notyfContext.options.types.map(type => { - if (type.type === 'success') { - type.background = theme.backgroundColor5; - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - (type.icon as any).color = theme.backgroundColor5; - } - return type; - }); - return output; - }, [notyfContext, theme]); +const onCancelClick = async (lastDeletion: StateLastDeletion) => { + if (lastDeletion.folderIds.length) { + await restoreItems(ModelType.Folder, lastDeletion.folderIds); + } - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - const onCancelClick = useCallback(async (event: any) => { - notyf.dismiss(notificationRef.current); - notificationRef.current = null; - - const lastDeletion: StateLastDeletion = JSON.parse(event.currentTarget.getAttribute('data-lastDeletion')); + if (lastDeletion.noteIds.length) { + await restoreItems(ModelType.Note, lastDeletion.noteIds); + } +}; - if (lastDeletion.folderIds.length) { - await restoreItems(ModelType.Folder, lastDeletion.folderIds); - } +export default (props: Props) => { + const popupManager = useContext(PopupNotificationContext); - if (lastDeletion.noteIds.length) { - await restoreItems(ModelType.Note, lastDeletion.noteIds); - } - }, [notyf]); + const lastDeletionNotificationTimeRef = useRef(); + lastDeletionNotificationTimeRef.current = props.lastDeletionNotificationTime; - useAsyncEffect(async (event) => { - if (!props.lastDeletion || props.lastDeletion.timestamp <= props.lastDeletionNotificationTime) return; + useEffect(() => { + const lastDeletionNotificationTime = lastDeletionNotificationTimeRef.current; + if (!props.lastDeletion || props.lastDeletion.timestamp <= lastDeletionNotificationTime) return; props.dispatch({ type: 'DELETION_NOTIFICATION_DONE' }); let msg = ''; - if (props.lastDeletion.folderIds.length) { msg = _('The notebook and its content was successfully moved to the trash.'); } else if (props.lastDeletion.noteIds.length) { @@ -70,16 +47,15 @@ export default (props: Props) => { return; } - const linkId = `deletion-notification-cancel-${Math.floor(Math.random() * 1000000)}`; - const cancelLabel = _('Cancel'); - - const notification = notyf.success(`${msg} ${cancelLabel}`); - notificationRef.current = notification; - - const element: HTMLAnchorElement = await waitForElement(document, linkId); - if (event.cancelled) return; - element.addEventListener('click', onCancelClick); - }, [props.lastDeletion, notyf, props.dispatch]); + const handleCancelClick = () => { + notification.remove(); + void onCancelClick(props.lastDeletion); + }; + const notification = popupManager.createPopup(() => ( + + ), NotificationType.Success); + notification.scheduleRemove(); + }, [props.lastDeletion, props.dispatch, popupManager]); return
; }; diff --git a/packages/app-desktop/gui/TrashNotification/TrashNotificationMessage.tsx b/packages/app-desktop/gui/TrashNotification/TrashNotificationMessage.tsx new file mode 100644 index 00000000000..f60b601af68 --- /dev/null +++ b/packages/app-desktop/gui/TrashNotification/TrashNotificationMessage.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import { _ } from '@joplin/lib/locale'; +import { useCallback, useState } from 'react'; + +interface Props { + message: string; + onCancel: ()=> void; +} + +const TrashNotificationMessage: React.FC = props => { + const [cancelling, setCancelling] = useState(false); + const onCancel = useCallback(() => { + setCancelling(true); + props.onCancel(); + }, [props.onCancel]); + + return <> + {props.message} + {' '} + + ; +}; + +export default TrashNotificationMessage; diff --git a/packages/app-desktop/gui/styles/index.scss b/packages/app-desktop/gui/styles/index.scss index e21bc051199..cb0920c7fe2 100644 --- a/packages/app-desktop/gui/styles/index.scss +++ b/packages/app-desktop/gui/styles/index.scss @@ -3,6 +3,7 @@ @use './user-webview-dialog.scss'; @use './prompt-dialog.scss'; @use './flat-button.scss'; +@use './link-button.scss'; @use './help-text.scss'; @use './toolbar-button.scss'; @use './toolbar-icon.scss'; @@ -12,3 +13,5 @@ @use './note-editor-wrapper.scss'; @use './text-input.scss'; @use './change-app-layout-dialog.scss'; +@use './popup-notification-list.scss'; +@use './popup-notification-item.scss'; diff --git a/packages/app-desktop/gui/styles/link-button.scss b/packages/app-desktop/gui/styles/link-button.scss new file mode 100644 index 00000000000..e10d040dc3d --- /dev/null +++ b/packages/app-desktop/gui/styles/link-button.scss @@ -0,0 +1,13 @@ + +.link-button { + background: transparent; + border: none; + font-size: inherit; + font-weight: inherit; + color: inherit; + padding: 0; + margin: 0; + + text-decoration: underline; + cursor: pointer; +} diff --git a/packages/app-desktop/gui/styles/popup-notification-item.scss b/packages/app-desktop/gui/styles/popup-notification-item.scss new file mode 100644 index 00000000000..cd35822e247 --- /dev/null +++ b/packages/app-desktop/gui/styles/popup-notification-item.scss @@ -0,0 +1,117 @@ +@keyframes slide-in { + from { + opacity: 0; + transform: translateY(25%); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slide-out { + from { + opacity: 1; + transform: translateY(0); + } + to { + opacity: 0; + transform: translateY(25%); + } +} + +@keyframes grow { + from { + transform: scale(0); + } + to { + transform: scale(1); + } +} + +.popup-notification-item { + margin: 13px; + padding: 17px 15px; + border-radius: 4px; + + overflow: hidden; + position: relative; + display: flex; + align-items: center; + + box-shadow: 0 3px 7px 0px rgba(0, 0, 0, 0.25); + + --text-color: var(--joplin-color3); + --ripple-color: var(--joplin-background-color3); + background-color: color-mix(in srgb, var(--ripple-color) 20%, transparent 70%); + color: var(--text-color); + + animation: slide-in 0.3s ease-in both; + + > .icon { + font-size: 14px; + text-align: center; + + width: 24px; + height: 24px; + // Make the line hight slightly larger than the icon size + // to vertically center the text + line-height: 26px; + + margin-right: 13px; + border-radius: 50%; + + color: var(--ripple-color); + background-color: var(--text-color); + } + + > .content { + padding: 10px 0; + max-width: min(280px, 70vw); + font-size: 1.1em; + font-weight: 500; + } + + > .ripple { + --ripple-size: 500px; + + position: absolute; + transform-origin: bottom right; + top: calc(var(--ripple-size) / -2); + right: -40px; + z-index: -1; + + background-color: var(--ripple-color); + width: var(--ripple-size); + height: var(--ripple-size); + border-radius: calc(var(--ripple-size) / 2); + + transform: scale(0); + animation: grow 0.4s ease-out forwards; + } + + &.-dismissing { + opacity: 0; + transform: translateY(25%); + animation: slide-out 0.4s ease-out forwards; + } + + &.-success { + --text-color: white; + --ripple-color: var(--joplin-color-correct); + } + + &.-error { + --text-color: white; + --ripple-color: var(--joplin-color-error); + } + + @media (prefers-reduced-motion) { + transform: none; + + > .ripple { + transform: scale(1); + animation: none; + } + } +} diff --git a/packages/app-desktop/gui/styles/popup-notification-list.scss b/packages/app-desktop/gui/styles/popup-notification-list.scss new file mode 100644 index 00000000000..fc290ac814b --- /dev/null +++ b/packages/app-desktop/gui/styles/popup-notification-list.scss @@ -0,0 +1,14 @@ + +.popup-notification-list { + position: absolute; + bottom: 0; + right: 0; + z-index: 10; + + display: flex; + // Focus should jump to the bottom item first + flex-direction: column-reverse; + + max-height: 100vh; + overflow-y: auto; +} From f2dcd1405c7753e5e728deeb1588ab1705b5b882 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Wed, 29 Jan 2025 14:01:10 -0800 Subject: [PATCH 02/19] Make the fade-out animation more closely match the original Notyf animation --- .../PopupNotificationProvider.tsx | 2 +- .../gui/styles/popup-notification-item.scss | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/app-desktop/gui/PopupNotification/PopupNotificationProvider.tsx b/packages/app-desktop/gui/PopupNotification/PopupNotificationProvider.tsx index 6ecbf17277a..d7170b04edb 100644 --- a/packages/app-desktop/gui/PopupNotification/PopupNotificationProvider.tsx +++ b/packages/app-desktop/gui/PopupNotification/PopupNotificationProvider.tsx @@ -31,7 +31,7 @@ const PopupNotificationProvider: React.FC = props => { })); // Remove the popup - const dismissAnimationDelay = 500; + const dismissAnimationDelay = 600; setTimeout(() => { setPopupSpecs(popups => popups.filter(p => p.key !== event.key)); }, dismissAnimationDelay); diff --git a/packages/app-desktop/gui/styles/popup-notification-item.scss b/packages/app-desktop/gui/styles/popup-notification-item.scss index cd35822e247..403aa4d8432 100644 --- a/packages/app-desktop/gui/styles/popup-notification-item.scss +++ b/packages/app-desktop/gui/styles/popup-notification-item.scss @@ -91,9 +91,13 @@ } &.-dismissing { - opacity: 0; - transform: translateY(25%); - animation: slide-out 0.4s ease-out forwards; + // Animate the icon and content first + animation: slide-out 0.25s ease-out both; + animation-delay: 0.25s; + + & > .content, & > .icon { + animation: slide-out 0.3s ease-out both; + } } &.-success { @@ -107,7 +111,9 @@ } @media (prefers-reduced-motion) { - transform: none; + &, & > .content, & > .icon { + transform: none !important; + } > .ripple { transform: scale(1); From 441ab4012c505086390d1cea6801ed0023f3e9ab Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Wed, 29 Jan 2025 14:09:02 -0800 Subject: [PATCH 03/19] Use new notification component for plugin notifications --- .../PluginNotification/PluginNotification.tsx | 26 +++++++++---------- .../PopupNotification/NotificationItem.tsx | 1 + .../PopupNotificationProvider.tsx | 2 +- .../gui/PopupNotification/types.ts | 7 ++++- .../TrashNotification/TrashNotification.tsx | 9 ++++--- 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/packages/app-desktop/gui/PluginNotification/PluginNotification.tsx b/packages/app-desktop/gui/PluginNotification/PluginNotification.tsx index 08e096edfc3..67db13dba75 100644 --- a/packages/app-desktop/gui/PluginNotification/PluginNotification.tsx +++ b/packages/app-desktop/gui/PluginNotification/PluginNotification.tsx @@ -1,8 +1,8 @@ -import { useContext, useMemo } from 'react'; -import NotyfContext from '../NotyfContext'; -import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect'; +import * as React from 'react'; +import { useContext, useEffect, useMemo } from 'react'; import { Toast, ToastType } from '@joplin/lib/services/plugins/api/types'; -import { INotyfNotificationOptions } from 'notyf'; +import { PopupNotificationContext } from '../PopupNotification/PopupNotificationProvider'; +import { NotificationType } from '../PopupNotification/types'; const emptyToast = (): Toast => { return { @@ -19,23 +19,21 @@ interface Props { } export default (props: Props) => { - const notyfContext = useContext(NotyfContext); + const popupManager = useContext(PopupNotificationContext); + const toast = useMemo(() => { const toast: Toast = props.toast ? props.toast : emptyToast(); return toast; }, [props.toast]); - useAsyncEffect(async () => { + useEffect(() => { if (!toast.message) return; - const options: Partial = { - type: toast.type, - message: toast.message, - duration: toast.duration, - }; - - notyfContext.open(options); - }, [toast.message, toast.duration, toast.type, notyfContext]); + popupManager.createPopup({ + type: toast.type as string as NotificationType, + content: () => <>{toast.message}, + }).scheduleRemove(toast.duration); + }, [toast.message, toast.duration, toast.type, popupManager]); return
; }; diff --git a/packages/app-desktop/gui/PopupNotification/NotificationItem.tsx b/packages/app-desktop/gui/PopupNotification/NotificationItem.tsx index 61aa6ee7f67..4446bcbb751 100644 --- a/packages/app-desktop/gui/PopupNotification/NotificationItem.tsx +++ b/packages/app-desktop/gui/PopupNotification/NotificationItem.tsx @@ -24,6 +24,7 @@ const NotificationItem: React.FC = props => { const containerModifier = (() => { if (props.type === NotificationType.Success) return '-success'; if (props.type === NotificationType.Error) return '-error'; + if (props.type === NotificationType.Info) return '-info'; return ''; })(); diff --git a/packages/app-desktop/gui/PopupNotification/PopupNotificationProvider.tsx b/packages/app-desktop/gui/PopupNotification/PopupNotificationProvider.tsx index d7170b04edb..76427f00e2c 100644 --- a/packages/app-desktop/gui/PopupNotification/PopupNotificationProvider.tsx +++ b/packages/app-desktop/gui/PopupNotification/PopupNotificationProvider.tsx @@ -39,7 +39,7 @@ const PopupNotificationProvider: React.FC = props => { const popupManager = useMemo((): PopupManager => { return { - createPopup(content, type: NotificationType): PopupHandle { + createPopup({ content, type }): PopupHandle { const key = `popup-${nextPopupKey.current++}`; const newPopup = { key, diff --git a/packages/app-desktop/gui/PopupNotification/types.ts b/packages/app-desktop/gui/PopupNotification/types.ts index 6483b11c64a..40ad4b88a96 100644 --- a/packages/app-desktop/gui/PopupNotification/types.ts +++ b/packages/app-desktop/gui/PopupNotification/types.ts @@ -11,6 +11,11 @@ export enum NotificationType { Error = 'error', } +export interface PopupOptions { + content: ()=> React.ReactNode; + type?: NotificationType; +} + export interface PopupControl { - createPopup(content: ()=> React.ReactNode, type: NotificationType): PopupHandle; + createPopup(props: PopupOptions): PopupHandle; } diff --git a/packages/app-desktop/gui/TrashNotification/TrashNotification.tsx b/packages/app-desktop/gui/TrashNotification/TrashNotification.tsx index 532b2f09005..fa82a2bd289 100644 --- a/packages/app-desktop/gui/TrashNotification/TrashNotification.tsx +++ b/packages/app-desktop/gui/TrashNotification/TrashNotification.tsx @@ -51,9 +51,12 @@ export default (props: Props) => { notification.remove(); void onCancelClick(props.lastDeletion); }; - const notification = popupManager.createPopup(() => ( - - ), NotificationType.Success); + const notification = popupManager.createPopup({ + content: () => ( + + ), + type: NotificationType.Success, + }); notification.scheduleRemove(); }, [props.lastDeletion, props.dispatch, popupManager]); From 0beb2db806a92c4819d89b4fdaefdc4676e5c2e1 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Wed, 29 Jan 2025 15:24:41 -0800 Subject: [PATCH 04/19] Use custom notifications for updates --- .../PopupNotification/NotificationItem.tsx | 3 +- .../UpdateNotification/UpdateNotification.tsx | 139 ++++++------------ .../gui/UpdateNotification/style.scss | 42 +++--- .../gui/styles/popup-notification-item.scss | 5 + 4 files changed, 74 insertions(+), 115 deletions(-) diff --git a/packages/app-desktop/gui/PopupNotification/NotificationItem.tsx b/packages/app-desktop/gui/PopupNotification/NotificationItem.tsx index 4446bcbb751..49425a6fd9c 100644 --- a/packages/app-desktop/gui/PopupNotification/NotificationItem.tsx +++ b/packages/app-desktop/gui/PopupNotification/NotificationItem.tsx @@ -28,11 +28,12 @@ const NotificationItem: React.FC = props => { return ''; })(); + const icon =