Skip to content

Commit

Permalink
Merge pull request #26696 from storybookjs/improve-notifications
Browse files Browse the repository at this point in the history
Core: Add `duration` and `onClick` support to Notification API and improve Notification UI
  • Loading branch information
ghengeveld authored Apr 3, 2024
2 parents fbd2aaf + 63254fd commit e0c5ff4
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 92 deletions.
35 changes: 17 additions & 18 deletions code/lib/manager-api/src/modules/notifications.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { API_Notification } from '@storybook/types';
import partition from 'lodash/partition.js';
import type { ModuleFn } from '../lib/types';

export interface SubState {
Expand All @@ -25,26 +26,24 @@ export interface SubAPI {

export const init: ModuleFn = ({ store }) => {
const api: SubAPI = {
addNotification: (notification) => {
// Get rid of it if already exists
api.clearNotification(notification.id);

const { notifications } = store.getState();

store.setState({ notifications: [...notifications, notification] });
addNotification: (newNotification) => {
store.setState(({ notifications }) => {
const [existing, others] = partition(notifications, (n) => n.id === newNotification.id);
existing.forEach((notification) => {
if (notification.onClear) notification.onClear({ dismissed: false, timeout: false });
});
return { notifications: [...others, newNotification] };
});
},

clearNotification: (id) => {
const { notifications } = store.getState();

const notification = notifications.find((n) => n.id === id);

if (notification) {
store.setState({ notifications: notifications.filter((n) => n.id !== id) });
if (notification.onClear) {
notification.onClear({ dismissed: false });
}
}
clearNotification: (notificationId) => {
store.setState(({ notifications }) => {
const [matching, others] = partition(notifications, (n) => n.id === notificationId);
matching.forEach((notification) => {
if (notification.onClear) notification.onClear({ dismissed: false, timeout: false });
});
return { notifications: others };
});
},
};

Expand Down
31 changes: 15 additions & 16 deletions code/lib/manager-api/src/tests/notifications.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,33 @@ import { describe, it, expect, vi } from 'vitest';
import { init as initNotifications } from '../modules/notifications';

describe('notifications API', () => {
it('allows adding notifications', () => {
const store = {
getState: () => ({
notifications: [],
}),
setState: vi.fn(),
};
const store = {
state: { notifications: [] },
getState: () => store.state,
setState: (update) => {
if (typeof update === 'function') {
store.state = update(store.state);
} else {
store.state = update;
}
},
};

it('allows adding notifications', () => {
const { api } = initNotifications({ store });

api.addNotification({ id: '1' });
expect(store.setState).toHaveBeenCalledWith({
expect(store.getState()).toEqual({
notifications: [{ id: '1' }],
});
});

it('allows removing notifications', () => {
const store = {
getState: () => ({
notifications: [{ id: '1' }, { id: '2' }, { id: '3' }],
}),
setState: vi.fn(),
};

store.setState({ notifications: [{ id: '1' }, { id: '2' }, { id: '3' }] });
const { api } = initNotifications({ store });

api.clearNotification('2');
expect(store.setState).toHaveBeenCalledWith({
expect(store.getState()).toEqual({
notifications: [{ id: '1' }, { id: '3' }],
});
});
Expand Down
17 changes: 15 additions & 2 deletions code/lib/types/src/modules/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,20 @@ export interface API_SidebarOptions {

interface OnClearOptions {
/**
* True when the user dismissed the notification.
* True when the user manually dismissed the notification.
*/
dismissed: boolean;
/**
* True when the notification timed out after the set duration.
*/
timeout: boolean;
}

interface OnClickOptions {
/**
* Function to dismiss the notification.
*/
onDismiss: () => void;
}

/**
Expand All @@ -130,14 +141,16 @@ interface DeprecatedIconType {
}
export interface API_Notification {
id: string;
link: string;
content: {
headline: string;
subHeadline?: string | any;
};
duration?: number;
link?: string;
// TODO: Remove DeprecatedIconType in 9.0
icon?: React.ReactNode | DeprecatedIconType;
onClear?: (options: OnClearOptions) => void;
onClick?: (options: OnClickOptions) => void;
}

type API_Versions = Record<string, string>;
Expand Down
Loading

0 comments on commit e0c5ff4

Please sign in to comment.