Skip to content

Commit

Permalink
Merge pull request #98 from hemengke1997/feat/global-css
Browse files Browse the repository at this point in the history
feat: refactor css modules to global BEM class
  • Loading branch information
almond-bongbong authored Sep 1, 2024
2 parents b10aa19 + 5c335a1 commit df47c5c
Show file tree
Hide file tree
Showing 36 changed files with 372 additions and 899 deletions.
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Elevate your React applications with ultra-sleek toast notifications! With React
</p>

## Table of Contents

- [Installation](#Installation)
- [Usage](#Usage)
- [Key Features](#Key-Features)
Expand All @@ -24,37 +25,35 @@ Elevate your React applications with ultra-sleek toast notifications! With React
- [License](#License)

<a name="Installation"></a>

## Installation 📦

Get started in seconds!



```bash
npm install react-simple-toasts
```

<a name="Usage"></a>

## Usage 💡

Integrate with ease. Customize with flair.

```jsx
import toast, { toastConfig } from 'react-simple-toasts';
import 'react-simple-toasts/dist/style.css';
import 'react-simple-toasts/dist/theme/dark.css';

toastConfig({ theme: 'dark' });

function MyComponent() {
return (
<button onClick={() => toast('Your toast is ready! 🍞')}>
Show Toast
</button>
);
return <button onClick={() => toast('Your toast is ready! 🍞')}>Show Toast</button>;
}
```

<a name="Key-Features"></a>

## Key Features 🌟

- **Ease of Use**: Set up in minutes, not hours!
Expand All @@ -64,31 +63,37 @@ function MyComponent() {
- **Multi-Toast Control**: Manage multiple notifications effortlessly.

<a name="Themes"></a>

## Themes 🎨

Your style, your toast. Choose from built-in themes or create your own.

### Standard Theme

<p align="center">
<img src="https://raw.githubusercontent.com/almond-bongbong/react-simple-toasts/master/docs/theme_standard.gif" alt="standard theme showcase" />
</p>

### Creative Theme

<p align="center">
<img src="https://raw.githubusercontent.com/almond-bongbong/react-simple-toasts/master/docs/theme_creative.gif" alt="creative theme showcase" />
</p>

<a name="Documentation"></a>

## Documentation 📘

Explore full [documentation](https://almond-bongbong.github.io/react-simple-toasts/) for in-depth guides, examples, and API details.

<a name="Contribute"></a>

## Contribute 🤝

Join our growing community! [Star us on GitHub](https://github.com/almond-bongbong/react-simple-toasts/stargazers) and contribute to making React Simple Toasts better.

<a name="License"></a>

## License 📜

React Simple Toasts is MIT licensed.
30 changes: 18 additions & 12 deletions __tests__/create-toast.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { createToast } from '../src';
import { ToastPosition } from '../src/lib/constants';
import { act, screen, waitForElementToBeRemoved } from '@testing-library/react';
import { act, fireEvent, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react';
import { generateMessage } from '../src/lib/utils';

const EXIT_ANIMATION_DURATION = 310;
const EXIT_ANIMATION_DURATION = 300;

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

describe('createToast', () => {
it('allows creating custom toast instances with specified classNames and overriding className', async () => {
Expand All @@ -27,25 +29,27 @@ describe('createToast', () => {

it('allows creating custom toast instances with specified durations and overriding duration', async () => {
const TOAST_TEXT = generateMessage();
const DURATION = 1000;
const DURATION = 500;
const myToast = createToast({
duration: DURATION,
});
await act(() => myToast(TOAST_TEXT));

const toastElement = screen.getByText(TOAST_TEXT);
await waitForElementToBeRemoved(toastElement, {
timeout: DURATION + EXIT_ANIMATION_DURATION,
});
await waitFor(() => delay(DURATION + EXIT_ANIMATION_DURATION));
fireEvent.transitionEnd(toastElement);

expect(toastElement).not.toBeInTheDocument();

const TOAST_TEXT_2 = generateMessage();
const DURATION_2 = 500;
const DURATION_2 = 300;
await act(() => myToast(TOAST_TEXT_2, { duration: DURATION_2 }));

const toastElement2 = screen.getByText(TOAST_TEXT_2);
await waitForElementToBeRemoved(toastElement2, {
timeout: DURATION_2 + EXIT_ANIMATION_DURATION,
});
await waitFor(() => delay(DURATION_2 + EXIT_ANIMATION_DURATION));
fireEvent.transitionEnd(toastElement2);

expect(toastElement2).not.toBeInTheDocument();
});

it('allows creating custom toast instances with specified positions and overriding position', async () => {
Expand All @@ -57,14 +61,16 @@ describe('createToast', () => {
await act(() => myToast(TOAST_TEXT));

const toastElement = screen.getByText(TOAST_TEXT);
expect(toastElement.parentElement).toHaveClass(POSITION);
expect(toastElement.parentElement).toHaveClass(`toast__message--${POSITION}`);

const TOAST_TEXT_2 = generateMessage();
const POSITION_2 = ToastPosition.BOTTOM_RIGHT;
await act(() => myToast(TOAST_TEXT_2, { position: POSITION_2 }));

const overridenPositionToastElement = screen.getByText(TOAST_TEXT_2);
expect(overridenPositionToastElement.parentElement).toHaveClass(POSITION_2);
expect(overridenPositionToastElement.parentElement).toHaveClass(
`toast__message--${POSITION_2}`,
);
});

it('allows creating custom toast instances with specified clickClosable and overriding clickClosable', async () => {
Expand Down
89 changes: 52 additions & 37 deletions __tests__/toast.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import React from 'react';
import { act, render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react';
import themeModuleClassNames from '../src/theme/theme-classnames.json';
import toast, { toast as toastNamed } from '../src';
import { generateMessage } from '../src/lib/utils';

const EXIT_ANIMATION_DURATION = 320;
const EXIT_ANIMATION_DURATION = 300;

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

describe('toast', () => {
it('renders a toast when the show button is clicked', async () => {
Expand All @@ -27,9 +28,11 @@ describe('toast', () => {
await act(() => toast(TOAST_TEXT, DURATION));

const toastElement = screen.getByText(TOAST_TEXT);
await waitForElementToBeRemoved(toastElement, {
timeout: DURATION + EXIT_ANIMATION_DURATION,
});

await waitFor(() => delay(DURATION + EXIT_ANIMATION_DURATION));
fireEvent.transitionEnd(toastElement);

expect(toastElement).not.toBeInTheDocument();
});

it('renders toast with infinite duration until manually closed', async () => {
Expand All @@ -38,9 +41,11 @@ describe('toast', () => {

const toastElement = screen.getByText(TOAST_TEXT);
await act(() => infiniteToast.close());
await waitForElementToBeRemoved(toastElement, {
timeout: EXIT_ANIMATION_DURATION,
});

await act(() => delay(EXIT_ANIMATION_DURATION));
fireEvent.transitionEnd(toastElement);

expect(toastElement).not.toBeInTheDocument();
});

it('renders and removes toast based on specified duration in options', async () => {
Expand All @@ -49,9 +54,10 @@ describe('toast', () => {
await act(() => toast(TOAST_TEXT, { duration: DURATION }));

const toastElement = screen.getByText(TOAST_TEXT);
await waitForElementToBeRemoved(toastElement, {
timeout: DURATION + EXIT_ANIMATION_DURATION,
});
await waitFor(() => delay(DURATION + EXIT_ANIMATION_DURATION));
fireEvent.transitionEnd(toastElement);

expect(toastElement).not.toBeInTheDocument();
});

it('applies custom className to toast container', async () => {
Expand All @@ -70,35 +76,37 @@ describe('toast', () => {
const toastDOM = screen.getByText(TOAST_TEXT);
await act(() => toastDOM.click());

await waitForElementToBeRemoved(toastDOM, {
timeout: EXIT_ANIMATION_DURATION,
});
await waitFor(() => delay(EXIT_ANIMATION_DURATION));
fireEvent.transitionEnd(toastDOM);

expect(toastDOM).not.toBeInTheDocument();
});

it('renders toast with specified position', async () => {
const TOAST_TEXT = generateMessage();
await act(() => toast(TOAST_TEXT, { position: 'top-center' }));
const toastDOM1 = screen.getByText(TOAST_TEXT);

expect(toastDOM1.parentElement).toHaveClass('top-center');
expect(toastDOM1.parentElement).toHaveClass('toast__message--top-center');
});

it('limits visible toasts based on maxVisibleToasts', async () => {
const TOAST_TEXT = generateMessage();
const DURATION = 500;

await act(() => {
toast(TOAST_TEXT);
toast(TOAST_TEXT, { maxVisibleToasts: 1 });
toast(TOAST_TEXT, DURATION);
toast(TOAST_TEXT, { duration: DURATION, maxVisibleToasts: 1 });
});

await waitFor(
() => {
const toasts = screen.getAllByText(TOAST_TEXT);
expect(toasts.length).toBe(1);
},
{
timeout: EXIT_ANIMATION_DURATION,
},
);
const toastDOM = screen.getAllByText(TOAST_TEXT);

toastDOM.forEach((toast) => {
fireEvent.transitionEnd(toast);
});

const toasts = screen.getAllByText(TOAST_TEXT);
expect(toasts.length).toBe(1);
});

it('renders custom toast content with render prop', async () => {
Expand All @@ -122,12 +130,17 @@ describe('toast', () => {

it('calls onCloseStart and onClose when toast is clicked with clickClosable set to true', async () => {
const TOAST_TEXT = generateMessage();
const DURATION = 500;
const onCloseStart = jest.fn();
const onClose = jest.fn();
await act(() => toast(TOAST_TEXT, { onCloseStart, onClose, clickClosable: true }));
await act(() =>
toast(TOAST_TEXT, { onCloseStart, onClose, clickClosable: true, duration: DURATION }),
);

const toastDOM = screen.getByText(TOAST_TEXT);
await act(() => toastDOM.click());
fireEvent.transitionEnd(toastDOM);

expect(onCloseStart).toHaveBeenCalled();
await waitFor(() => expect(onClose).toHaveBeenCalled());
});
Expand All @@ -141,9 +154,10 @@ describe('toast', () => {
toastInstance.updateDuration(NEW_DURATION);
const toastElement = screen.getByText(TOAST_TEXT);

await waitForElementToBeRemoved(toastElement, {
timeout: NEW_DURATION + EXIT_ANIMATION_DURATION,
});
await act(() => delay(NEW_DURATION));
fireEvent.transitionEnd(toastElement);

expect(toastElement).not.toBeInTheDocument();
});

it('updates the content of the displayed toast', async () => {
Expand All @@ -161,7 +175,7 @@ describe('toast', () => {
await act(() => toast(TOAST_TEXT, { theme: 'light' }));
const toastElement = screen.getByText(TOAST_TEXT);

expect(toastElement).toHaveClass(themeModuleClassNames['toast-light']);
expect(toastElement).toHaveClass('toast__light');
});

it('applies the specified zIndex to the toast', async () => {
Expand All @@ -182,9 +196,10 @@ describe('toast', () => {
);
const toastElement = screen.getByText(TOAST_TEXT);
await act(() => toastElement.click());
await waitForElementToBeRemoved(toastElement, {
timeout: EXIT_ANIMATION_DURATION,
});
await waitFor(() => delay(EXIT_ANIMATION_DURATION));
fireEvent.transitionEnd(toastElement);

expect(toastElement).not.toBeInTheDocument();
});

it('renders second toast upper than first toast when isReversedOrder set to true', async () => {
Expand All @@ -198,7 +213,7 @@ describe('toast', () => {

it('applies theme class to toast when theme is specified and does not apply it when theme is not specified', async () => {
const TOAST_TEXT = generateMessage();
const toastContentClassName = 'toast-theme-content';
const toastContentClassName = 'toast__content toast__theme-content';
await act(() => toast(TOAST_TEXT, { theme: 'dark' }));

const toastDOM = screen.getByText(TOAST_TEXT);
Expand Down Expand Up @@ -261,7 +276,7 @@ describe('toast', () => {
it('should retain the theme when updating the toast with an options object', async () => {
const TOAST_TEXT = generateMessage();
const UPDATED_TEXT = generateMessage();
const toastContentClassName = 'toast-theme-content';
const toastContentClassName = 'toast__theme-content';

const toastInstance = await act(() => toast(TOAST_TEXT, { theme: 'dark' }));

Expand Down
1 change: 1 addition & 0 deletions example/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client';
import App from './app';
import './index.css';
import { toastConfig } from 'react-simple-toasts';
import 'react-simple-toasts/dist/style.css';

import.meta.globEager('/node_modules/react-simple-toasts/dist/theme/*.css');

Expand Down
2 changes: 1 addition & 1 deletion example/src/page/api/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function Api() {
</p>
<div className={styles.code}>
<CommonHighlighter>{`import toast from 'react-simple-toasts';
import 'react-simple-toasts/dist/style.css';
export function MyComponent() {
return (
<button onClick={() => toast('Hello, world!')}>
Expand Down
Loading

0 comments on commit df47c5c

Please sign in to comment.