Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ For current development version of Console, use `4.x.0-prerelease.n` packages.
For older 1.x plugin SDK packages, refer to "OpenShift Console Versions vs SDK Versions" compatibility
table in [Console dynamic plugins README](./README.md).

## 4.23.0-prerelease.5 - TBD

- Add minimize action for toast notifications via new `ToastOptions.minimizable`, `ToastOptions.actions[].minimize`, and `ToastContextValues.minimizeToast` ([CONSOLE-5424], [#16762])

## 4.23.0-prerelease.4 - 2026-07-14

- Add optional `onSubmit` parameter to `useLabelsModal` hook for customizing label submission behavior ([CONSOLE-5356], [#16560])
Expand Down Expand Up @@ -246,6 +250,7 @@ table in [Console dynamic plugins README](./README.md).
[CONSOLE-5356]: https://issues.redhat.com/browse/CONSOLE-5356
[CONSOLE-5361]: https://issues.redhat.com/browse/CONSOLE-5361
[CONSOLE-5415]: https://issues.redhat.com/browse/CONSOLE-5415
[CONSOLE-5424]: https://issues.redhat.com/browse/CONSOLE-5424
[OCPBUGS-19048]: https://issues.redhat.com/browse/OCPBUGS-19048
[OCPBUGS-30077]: https://issues.redhat.com/browse/OCPBUGS-30077
[OCPBUGS-31355]: https://issues.redhat.com/browse/OCPBUGS-31355
Expand Down Expand Up @@ -339,3 +344,4 @@ table in [Console dynamic plugins README](./README.md).
[#16636]: https://github.com/openshift/console/pull/16636
[#16726]: https://github.com/openshift/console/pull/16726
[#16750]: https://github.com/openshift/console/pull/16750
[#16762]: https://github.com/openshift/console/pull/16762
2 changes: 1 addition & 1 deletion frontend/packages/console-dynamic-plugin-sdk/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3210,7 +3210,7 @@ const Component: React.FC = (props) => {

### Returns

A toast context object with an `addToast` function for adding a toast to the screen and a `removeToast` function for removing a toast from the screen.
A context object with an addToast function for adding a toast to the screen, a<br/>removeToast function for removing it from the screen, and a minimizeToast function for<br/>hiding a drawer-persisted toast from the screen while keeping it unread in the notification drawer.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

the breaks have to stay @galkremer1 ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, they are auto-generated by the content of frontend/packages/console-dynamic-plugin-sdk/src/api/core-api.ts



### Source
Expand Down
71 changes: 50 additions & 21 deletions frontend/packages/console-dynamic-plugin-sdk/release-notes/4.23.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,34 +61,63 @@ The UI displays tabs in priority order from highest to lowest. Default built-in

### Improved control over toast notifications

When displaying toast notifications using `useToast` hook, Console supports persisting toasts in the
notification drawer with read/unread state and management actions. Toast notifications that opt in to
drawer persistence are subject to a visible cap (default 3) with an overflow link to the notification
drawer. Existing toast consumers are unaffected — by default, toasts remain on-screen only.
When displaying toast notifications using the `useToast` hook, Console supports persisting toasts in the
notification drawer with read/unread state, management actions, and minimizing toasts back to the
drawer. Toast notifications that opt in to drawer persistence are subject to a visible cap (default 3)
with an overflow link to the notification drawer. Existing toast consumers are unaffected: by default,
toasts remain on-screen only.

`ToastOptions` supports optional properties for this control:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@jseseCCS can you please review this section again?


- `persistInDrawer` (default `false`) to persist the toast in the notification drawer with read/unread
state. Drawer-persisted toasts default to counting toward the visible cap and overflow link.
- `skipOverflow` (default `true`) to exclude the toast from the visible cap (default 3) and overflow
link. Defaults to `false` when `persistInDrawer` is `true`, unless set explicitly. Combine
`persistInDrawer: true` with `skipOverflow: true` for always-visible drawer-persisted toasts.
- `drawerGroup` to group the toast into a named drawer section. Omitted toasts use the built-in group,
displayed as "Other Alerts" (translated by Console). Custom values are shown as-is; plugins are
responsible for translating their own group names.
- `minimizable` (default `false`) to add a Minus icon button, for toasts that aren't `dismissible`, that
minimizes the toast: hides it on-screen and keeps it unread in the drawer, without invoking `onClose`.
Requires `persistInDrawer: true`.
- `actions` to add buttons or links to the toast, each with a `label` and `callback`. Set `dismiss: true`
on an action to also dismiss the toast when clicked, or `minimize: true` to minimize it instead. A
`minimize: true` action renders as a text link at its position in the array, useful for `dismissible`
toasts where the Minus icon isn't shown.

`useToast` also returns `minimizeToast(id)` for minimizing a toast programmatically.

#### Example: Persist, group, and minimize a toast

`ToastOptions` supports new optional properties for better control over toast notifications:
```tsx
const { addToast } = useToast();

- `persistInDrawer` (default `false`) to persist the toast in the notification drawer when set to `true`.
Drawer-persisted toasts default to participating in the visible toast cap and overflow link.
- `skipOverflow` (default `true`) to control whether a toast participates in the visible toast cap.
When `persistInDrawer` is `true`, defaults to `false` unless explicitly set to `true`.
Use `persistInDrawer: true` with `skipOverflow: true` for always-visible drawer-persisted toasts
(e.g. long-running upload progress).
- `drawerGroup` to group toast notifications into named sections in the notification drawer. When omitted,
toasts appear in the built-in default group, displayed as "Other Alerts" (translated by Console). Custom
`drawerGroup` values are shown as-is; plugins are responsible for translating their own group names if needed.
addToast({
title: 'Uploading disk.img',
variant: 'info',
content: <UploadProgressContent />,
dismissible: false,
timeout: false,
persistInDrawer: true,
skipOverflow: true,
drawerGroup: 'Uploads', // translate custom group names in your own plugin, e.g. t('Uploads')
minimizable: true,
});
```

### Example: persisting and grouping toasts in the notification drawer
#### Example: Order a minimize action for a dismissible toast

```tsx
const { addToast } = useToast();

addToast({
title: 'Upload complete',
variant: 'success',
content: 'disk.img uploaded successfully.',
title: 'Import failed',
variant: 'danger',
content: 'disk.img could not be imported.',
dismissible: true,
persistInDrawer: true,
drawerGroup: 'Uploads',
actions: [
{ label: 'Retry', callback: retryImport },
{ label: 'Minimize', minimize: true, callback: () => {} },
],
});
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -998,7 +998,9 @@ export const useActivePerspective: UseActivePerspective = require('@console/dyna

/**
* Hook that provides toast functionality for displaying alerts.
* @returns A toast context object with an `addToast` function for adding a toast to the screen and a `removeToast` function for removing a toast from the screen.
* @returns A context object with an addToast function for adding a toast to the screen, a
* removeToast function for removing it from the screen, and a minimizeToast function for
* hiding a drawer-persisted toast from the screen while keeping it unread in the notification drawer.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

same comment as in api.md line 3213

* @example
* ```tsx
* const Component: React.FC = (props) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,13 @@ export type ToastOptions = {
callback: () => void;
/** If `true`, executing this action will dismiss the toast. */
dismiss?: boolean;
/**
* If `true`, executing this action minimizes the toast: hides it from the on-screen alert group
* while keeping it unread in the notification drawer, without invoking `onClose` or canceling the
* underlying operation. Has no effect when the toast isn't persisted in the drawer
* (`persistInDrawer: true`). Takes precedence over `dismiss` when both are `true`.
*/
minimize?: boolean;
/** Sets the base component to render. defaults to button */
component?: ElementType<any> | ComponentType<any>;
/** The data test id */
Expand Down Expand Up @@ -668,13 +675,25 @@ export type ToastOptions = {
* Defaults to `false`.
*/
persistInDrawer?: boolean;
/**
* When `true` and `dismissible` is `false`, shows a Minimize icon button in the close button's slot.
* Minimizing hides the toast on-screen while keeping it unread in the drawer, without invoking
* `onClose`. No-op when `persistInDrawer` isn't `true`. Defaults to `false`.
*/
minimizable?: boolean;
};

export type ToastContextValues = {
/** Add a toast alert. Returns the toast ID. */
addToast: (options: ToastOptions) => string;
/** Remove a toast alert. */
removeToast: (id: string) => void;
/**
* Minimize a toast: hide it from the on-screen alert group while keeping it unread in the
* notification drawer history. Does not invoke `onClose`. No-op if the toast is not persisted
* in the drawer (`persistInDrawer: true`).
*/
minimizeToast: (id: string) => void;
};

export type UseToast = () => ToastContextValues;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@
"Healthy": "Healthy",
"View {{count}} more notification_one": "View {{count}} more notification",
"View {{count}} more notification_other": "View {{count}} more notifications",
"Minimize alert: {{title}}": "Minimize alert: {{title}}",
"Enable": "Enable",
"Disable": "Disable",
"Enabling console plugin": "Enabling console plugin",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
AlertActionCloseButton,
AlertActionLink,
AlertVariant,
Button,
} from '@patternfly/react-core';
import { RhUiMinusIcon } from '@patternfly/react-icons';
import { useTranslation } from 'react-i18next';
import type {
ToastOptions,
Expand Down Expand Up @@ -116,6 +118,22 @@ export const ToastProvider: FC<ToastProviderProps> = ({
);
}, []);

const minimizeToast = useCallback((id: string) => {
let canMinimize = false;
setToasts((state) => {
const toast = state.find((item) => item.id === id);
canMinimize = toast?.persistInDrawer === true;
return canMinimize ? state.filter((item) => item.id !== id) : state;
});
if (canMinimize) {
setNotifications((state) =>
state.map((notification) =>
notification.id === id ? { ...notification, isRead: false } : notification,
),
);
}
}, []);
Comment thread
galkremer1 marked this conversation as resolved.

const clearNotification = useCallback(
(id: string) => {
setNotifications((state) => {
Expand Down Expand Up @@ -170,8 +188,9 @@ export const ToastProvider: FC<ToastProviderProps> = ({
() => ({
addToast,
removeToast,
minimizeToast,
}),
[addToast, removeToast],
[addToast, removeToast, minimizeToast],
);

const notificationHistoryController = useMemo(
Expand Down Expand Up @@ -214,49 +233,69 @@ export const ToastProvider: FC<ToastProviderProps> = ({
overflowMessage={overflowMessage}
onOverflowClick={onOverflowClick}
>
{visibleToasts.map((toast) => (
<Alert
key={toast.id}
title={toast.title}
variant={toast.variant}
timeout={toast.timeout}
onTimeout={() => removeToast(toast.id)}
data-test={toast.dataTest || `${toast.title} alert`}
actionClose={
toast.dismissible ? (
<AlertActionCloseButton
onClose={() => {
toast.onClose && toast.onClose();
removeToast(toast.id);
}}
/>
) : undefined
}
actionLinks={
toast.actions?.length > 0 ? (
<>
{toast.actions.map((action) => (
<AlertActionLink
key={action.label}
onClick={() => {
if (action.dismiss) {
removeToast(toast.id);
{visibleToasts.map((toast) => {
const minimizeAsIconButton =
toast.minimizable && toast.persistInDrawer && !toast.dismissible;

return (
<Alert
key={toast.id}
title={toast.title}
variant={toast.variant}
timeout={toast.timeout}
onTimeout={() => removeToast(toast.id)}
data-test={toast.dataTest || `${toast.title} alert`}
actionClose={
toast.dismissible ? (
<AlertActionCloseButton
onClose={() => {
toast.onClose && toast.onClose();
removeToast(toast.id);
}}
/>
) : minimizeAsIconButton ? (
<Button
variant="plain"
aria-label={t('Minimize alert: {{title}}', { title: toast.title })}
icon={<RhUiMinusIcon />}
onClick={() => minimizeToast(toast.id)}
data-test={
toast.dataTest ? `${toast.dataTest}-minimize` : 'toast-minimize-action'
}
/>
) : undefined
}
actionLinks={
toast.actions?.length > 0 ? (
<>
{toast.actions.map((action) => (
<AlertActionLink
key={action.label}
onClick={() => {
if (action.minimize) {
minimizeToast(toast.id);
} else if (action.dismiss) {
removeToast(toast.id);
}
action.callback();
}}
component={action.component}
data-test={
action.dataTest ||
(action.minimize ? 'toast-minimize-action-link' : 'toast-action')
}
action.callback();
}}
component={action.component}
data-test={action.dataTest || 'toast-action'}
>
{action.label}
</AlertActionLink>
))}
</>
) : undefined
}
>
{toast.content}
</Alert>
))}
>
{action.label}
</AlertActionLink>
))}
</>
) : undefined
}
>
{toast.content}
</Alert>
);
})}
Comment thread
galkremer1 marked this conversation as resolved.
</AlertGroup>
) : null}
</NotificationHistoryContext.Provider>
Expand Down
Loading