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
14 changes: 13 additions & 1 deletion playground/src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class App extends React.Component {
...defaultProps,
transition: 'bounce',
type: 'default',
progress: '',
progress: 0,
disableAutoClose: false,
limit: 0,
theme: 'light'
Expand Down Expand Up @@ -71,6 +71,10 @@ class App extends React.Component {

updateToast = () => toast.update(this.toastId, { progress: this.state.progress });

showPriorityToast = () => {
toast('🥇 Priority toast! (prepend: true)', { prepend: true });
};

handleAutoCloseDelay = e =>
this.setState({
autoClose: e.target.value > 0 ? parseInt(e.target.value, 10) : 1
Expand Down Expand Up @@ -219,6 +223,14 @@ class App extends React.Component {
Show Toast
</button>
</li>
<li>
<button className="btn" onClick={this.showPriorityToast}>
<span role="img" aria-label="show priority alert">
🥇
</span>{' '}
Priority Toast (prepend)
</button>
</li>
<li>
<button className="btn" onClick={this.firePromise}>
Promise
Expand Down
20 changes: 17 additions & 3 deletions src/core/containerObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,23 @@ export function createContainerObserver(
};

const addActiveToast = (toast: Toast) => {
const { toastId, updateId } = toast.props;
const { toastId, updateId, prepend } = toast.props;
const isNew = updateId == null;

if (toast.staleId) toasts.delete(toast.staleId);
toast.isActive = true;

toasts.set(toastId, toast);
if (isNew && prepend && toasts.size > 0) {
// `Map` preserves insertion order, so to bring this toast to the front we
// need to rebuild it with the new entry first, followed by the rest untouched.
const entries = Array.from(toasts.entries()).filter(([id]) => id !== toastId);
toasts.clear();
toasts.set(toastId, toast);
entries.forEach(([id, t]) => toasts.set(id, t));
} else {
toasts.set(toastId, toast);
}

notify();
dispatchChanges(toToastItem(toast, isNew ? 'added' : 'updated'));

Expand Down Expand Up @@ -151,7 +161,11 @@ export function createContainerObserver(

// not handling limit + delay by design. Waiting for user feedback first
if (props.limit && props.limit > 0 && toastCount > props.limit && isNotAnUpdate) {
queue.push(activeToast);
if (options.prepend) {
queue.unshift(activeToast);
} else {
queue.push(activeToast);
}
} else if (isNum(delay)) {
setTimeout(() => {
addActiveToast(activeToast);
Expand Down
54 changes: 54 additions & 0 deletions src/core/toast.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,60 @@ describe('with container', () => {
});
});

describe('with prepend option', () => {
beforeEach(() => {
cy.mount(<ToastContainer autoClose={false} closeOnClick />);
});

it('inserts the toast before the ones already displayed', () => {
toast('msg1');
toast('msg2');
cy.resolveEntranceAnimation();

toast('msg3', { prepend: true });
cy.resolveEntranceAnimation();

cy.findAllByRole('alert').should('have.length', 3);
cy.findAllByRole('alert').eq(0).should('have.text', 'msg3');
cy.findAllByRole('alert').eq(1).should('have.text', 'msg1');
cy.findAllByRole('alert').eq(2).should('have.text', 'msg2');
});

it('does not affect the order when there is nothing displayed yet', () => {
toast('msg1', { prepend: true });
cy.resolveEntranceAnimation();

cy.findAllByRole('alert').should('have.length', 1);
cy.findAllByRole('alert').eq(0).should('have.text', 'msg1');
});
});

describe('with limit and prepend option', () => {
it('prepended toast becomes the next one to appear when the limit is reached', () => {
cy.mount(<ToastContainer autoClose={false} limit={2} closeOnClick />);

toast('msg1');
toast('msg2');
toast('msg3');
toast('msg4', { prepend: true });
cy.resolveEntranceAnimation();

cy.findByText('msg1').should('exist');
cy.findByText('msg2').should('exist');
cy.findByText('msg3').should('not.exist');
cy.findByText('msg4').should('not.exist');

cy.findByText('msg1')
.click()
.then(() => {
cy.resolveEntranceAnimation();

cy.findByText('msg4').should('exist');
cy.findByText('msg3').should('not.exist');
});
});
});

describe.skip('with multi containers', () => {
const Containers = {
First: 'first',
Expand Down
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,13 @@ export interface ToastOptions<Data = unknown> extends CommonOptions {
delay?: number;

isLoading?: boolean;

/**
* Insert the toast before any other toast currently displayed or waiting in the queue,
* instead of appending it after them.
* `Default: false`
*/
prepend?: boolean;
}

export interface UpdateOptions<T = unknown> extends Nullable<ToastOptions<T>> {
Expand Down