Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revamp README.md #101

Merged
merged 2 commits into from
Apr 15, 2023
Merged
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
263 changes: 174 additions & 89 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,90 +1,57 @@
# React Modal Global
<h1 align="center">📦 React Modal Global</h1>
<h3 align="center">Composite global modals for react, which may be used in `useEffect` or any other global scope 🚀</h3>

[![codecov](https://codecov.io/gh/FrameMuse/react-modal-global/branch/main/graph/badge.svg?token=1FRUN6AQDA)](https://codecov.io/gh/FrameMuse/react-modal-global)
[![npm version](https://badge.fury.io/js/react-modal-global.svg)](https://badge.fury.io/js/react-modal-global)
[![npm downloads](https://img.shields.io/npm/dm/react-modal-global.svg)](https://www.npmjs.com/package/react-modal-global)
[![GitHub license](https://img.shields.io/github/license/FrameMuse/react-modal-global)]()
<p align="center">
<a href="https://codecov.io/gh/FrameMuse/react-modal-global">
<img src="https://codecov.io/gh/FrameMuse/react-modal-global/branch/main/graph/badge.svg?token=1FRUN6AQDA" />
</a>
</p>
<p align="center">
<a href="https://www.npmjs.com/package/react-modal-global">
<img src="https://img.shields.io/npm/v/react-modal-global/latest.svg" />
</a>
<a href="https://www.npmjs.com/package/react-modal-global">
<img src="https://img.shields.io/npm/v/react-modal-global/experimental.svg" />
</a>
</p>

[![GitHub stars](https://img.shields.io/github/stars/FrameMuse/react-modal-global)]()
[![GitHub contributors](https://img.shields.io/github/contributors/FrameMuse/react-modal-global)]()
[![GitHub last commit](https://img.shields.io/github/last-commit/FrameMuse/react-modal-global)]()
## Features

## Presentation
- 🌍 Can be used in `useEffect` hook or any other global scope
- 💧 [Reactish](#open-as-it-is) - Open modals `Modal.open(ModalComponent, { id: "2" })` as you would render components `<ModalComponent id="2" />`
- 🪢 [Context](#using-modal-context) - you can access params passed to `open` method in the modal component via `useModalContext` hook
- 🔃 [Queueing](#queueing) - you can open several modals at once
- ✅ [Data preservation](#data-preservation) - data will persist after closing last modal and if same modal will be request to open, it will be restored
- 📚 [Layouts](#modal-layouts) - you can create your own layouts for each modal type (Dialog, Popup, Drawer).
- 🎛️ [Customization](#modal-controller) - you can extend `ModalController` class and create your behavior
- 🦑 [Forking](#layer-depth) - you can fork modals and create "layer depth" (_in development_)

React modal dialogs which is similar to [`react-modal`](https://www.npmjs.com/package/react-modal) but it may be called from `useEffect`, that's why it is **global** ^_^
## Motivation

## Contribute
I was looking for a way to use modals in `useEffect` hook without creating a new component for each one by passing `props` to `open` method. I found a lot of packages that allow to use modals in non-component context, but they all have some disadvantages:

Needs feedback, please contribute to GitHub Issues or leave your message to [my discord server](https://discord.gg/DCUWrRhvnt).

## Navigation

- [React Modal Global](#react-modal-global)
- [Presentation](#presentation)
- [Contribute](#contribute)
- [Navigation](#navigation)
- [Advantages](#advantages)
- [Major advantages](#major-advantages)
- [Minor advantages](#minor-advantages)
- [Usage](#usage)
- [Playgrounds](#playgrounds)
- [Example of layouts useage](#example-of-layouts-useage)
- [Example of usage with ChakraUI (by @laurensnl)](#example-of-usage-with-chakraui-by-laurensnl)
- [Add container](#add-container)
- [Create new Modal component](#create-new-modal-component)
- [Plain component](#plain-component)
- [Using `modal context`](#using-modal-context)
- [Modal component usage](#modal-component-usage)
- [Modal Template](#modal-template)
- [Modal layouts](#modal-layouts)
- [If using several containers](#if-using-several-containers)
- [Layout concept](#layout-concept)
- [Description](#description)
- [Aria](#aria)
- [Modal controller](#modal-controller)
- [`Open`](#open)
- [`Close`](#close)
- [`CloseByComponent`](#closebycomponent)
- [`CloseById`](#closebyid)
- [Modal options](#modal-options)

## Advantages

### Major advantages

- Allows to use modals in `useEffect` hook without creating a new component for each one by passing `props` to `open` method.
- Allows opening modals without wrapping them in components and controlling their state.
- Allows to use modals in non-component context (e.g. in `useEffect` hook).
- Allows to reuse modals in different places without creating a new component for each one by passing `props` to `open` method.
- Allows to use various modal types (Dialog, Popup, Drawer) by creating your own layout for each one (advised naming is `[Type][Name]` => `DrawerLayout`).
- Allows customizing modal controls by extending `ModalController` class and creating your own layouts.
- Allows to use several containers at different depths of your app (e.g. to vary templates).
- Allows forking modals and creating "layer depth" (_in development_).

### Minor advantages

- Globalization - opened from anywhere (even from non-component context)
- Context - data that passed in `open` method can be accessed in the component using `useModalContext` hook
- Stacking/Nesting (as a container option).
- Data preservation (after closing last modal, the data will be preserved and if same modal will be request to open, it will _restore_ previous modal but with `weak: true` it will not happen)
- `open` method is `PromiseLike` (`thenable`) - you can use `await` or `then` to wait for modal closing
- The package uses only react as a peer dependency
- They are not global (you can't open them from anywhere)
- They are not context-aware (you can't pass data to them)
- They are not queueing-aware (you can't open several modals at once)

## Usage
So I decided to create my own package that will solve all these problems.

### Playgrounds
I insipered a lot from packages like [react-toastify](https://npmjs.com/package/react-toastify) and [react-alert](https://npmjs.com/package/react-alert).

#### Example of layouts useage
## Playgrounds
| Title | Playground |
| --- | --- |
| Example of layouts usage | [![Edit react-modal-global](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/react-modal-global-examples-47yoil) |
| Example of usage with ChakraUI (by [@laurensnl](https://github.com/laurensnl)) | [![Edit react-modal-global](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/chakraui-nextjs-react-modal-global-h0g21f) |

[![Edit react-modal-global](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/react-modal-global-examples-47yoil)

#### Example of usage with ChakraUI (by [@laurensnl](https://github.com/laurensnl))
## Usage

[![Edit react-modal-global](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/chakraui-nextjs-react-modal-global-h0g21f)
Please follow steps below to use this package in your project.

### Add container

`ModalContainer` is a container for modal components (it usually appears in the root of your app) and modal components will appear there as you open them.
`ModalContainer` is a display container for modal components (it should be placed in the root), modal components will appear here as you open them.

```tsx
import React from "react"
Expand All @@ -103,26 +70,45 @@ function App() {
ReactDOM.render(<App />, document.getElementById("root"))
```

### !!! STYLES !!!

Notice that you have to have styles to keep modal closed, otherwise modal will not work as expected.

You can write your own styles or use provided be the library by importing it

```tsx
import "react-modal-global/styles/modal.scss"
```

### Create new Modal component

All it needs for creating modal component is a react component factory with a valid `JSX.Element`:
Modal component is actually a React component, however this should be exactly component not an element.

#### Plain component
#### React component

```tsx
// Arrow function
// Arrow function component
const ModalComponent = () => <>:3</>
// Plain function
// Plain function component
function ModalComponent() {
return <>:3</>
}
// Class component
import { Component } from "react"
class ModalComponent extends Component {
render() { return <>:3</> }
}
```

#### Using `modal context`

This allows a component to access modal window context inside it to see what props were passed in `open` method.

This also can be used to close modal window from inside (e.g. close on button click).

```tsx
function ModalComponent() {
const modal = useModalContext() // Getting modal context of currently active component
const modal = useModalContext() // Getting modal context of currently focused component

return (
<>
Expand All @@ -135,20 +121,31 @@ function ModalComponent() {
}
```

#### Advanced usage

Modal components can be templates for other modal components.
[See example here](#layout-concept)

#### Naming advice

Recommended naming is [Popup, Dialog or Modal] + [Name of a modal] => DialogPurchase.

### Modal component usage

Note that `PopupLogin` should have its own styles to look like a popup, it is advised to use custom `PopupLayout` (Learn below).
#### Open 'em

This is how you disaply your modal components

```tsx
import "react-modal-global/styles/modal.scss" // import default styles if want
// Import default styles.
import "react-modal-global/styles/modal.scss"

import { Modal } from "react-modal-global"

import PopupLogin from "./PopupLogin"

function HomeView() {
function showLoginPopup() {
// Recommended naming is [Popup, Dialog or Modal] then [Name of a modal] i.e. DialogMyName
Modal.open(PopupLogin, { /* Probably your options? */ })
}
return (
Expand All @@ -162,13 +159,35 @@ function HomeView() {
}
```

### Modal Template
#### Open as it is

Although you can create your own layout (i.e. component) to pass `title`, `description` and other required/optional props, to what you are used to. This library encourages using only component and its props to open modals as just how you would use it in a tree.

So instead of
```ts
Modal.open(PopupGeneral, {
title: "Login",
description: "Please login to continue"
})
```

Include them in the component itself. Of course you will need to create more components, but this is the way to go.

```ts
Modal.open(PopupLogin)
```
```

Or you can create types of your modal components and pass them to `open` method.

There is a multicontainers feature - you can put containers at different depths of your app to vary templates.
```ts
Modal.open(PopupAuth, {
type: "login" // or "register"
})
```

Only one container will be used.
Eventually, this is up to you to decide, it always depends on your case.

The last mounted container will be used.

### Modal layouts

Expand Down Expand Up @@ -274,8 +293,74 @@ Modal.closeById("insane-id")
You can use options when opening a modal with `Modal.open()`.
Available options

| Option | Description |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | Specifies id of a modal. In react it's used as a `key`. May be used to find and close specific modal or else. |
| `closable` | Specifies if a modal closing is controllable internally. If `false`, it's supposed to mean that user should do a **specific** action to close. |
| `weak` | By default, a last closed modal will not be removed if the same modal will be requested to open. It will _restore_ previous modal but with `weak: true` it will not happen. |
| Option | Description |
| --- | --- |
| `id` | Specifies id of a modal. In react it's used as a `key`. May be used to find and close specific modal or else. |
| `closable` | Specifies if a modal closing is controllable internally. If `false`, it's supposed to mean that user should do a **specific** action to close. |
| `weak` | By default, a last closed modal will not be removed if the same modal will be requested to open. It will _restore_ previous modal but with `weak: true` it will not happen. |

## Modal container

The `ModalContainer` component is a container for all modals, but it's not required to use it.

So if you're not happy with the default container, you can create your own.

It depends on `ModalController` and `ModalContext` to work.

## Other features

### Queueing

If you open a modal while another modal is already opened, it will be queued and will be opened after the previous one is closed.

### Data preservation

Data will be restored if you open a modal with the same component and props after it was closed.

It also works with `Modal.replace` method, which can replace props with new ones while keeping the same component and data.

### Layer depth

**---This Feature is still in development---**

Sometimes you need to open a modal on top of another modal, for example, you have a modal with a login form, and you want to open another modal with a phone confirmation on top of it but keeping the login form visible and state preserved.

This is where `layer` depth comes in handy.

```tsx
Modal.open(ModalComponent, {
layer: 1 // Default is 0
})
```

Note that `layer` depth is not a z-index, it's just a number that specifies the depth of a modal, it's used to determine which modal should be opened on top of another, which may be overriden by focusing another modal.

---

It's in early development and accessable in [@experimental](https://www.npmjs.com/package/react-modal-global/v/experimental) branch.
Please consider contributing to this feature, I will be happy to see your PRs or just a [feedback](#contribute).

## Other modal ideas

There are more than one way to create modals in React.

Two the most popular are "React Tree Modal" and "General Consumer Modal" (this is how I named it).

### React Tree Modal

This is mostly known in React community, it's a modal that is rendered in a tree. There is a library called [react-modal](https://npmjs.com/package/react-modal) that implements this approach.

But this way has some problems, one of them is that you have to create a component, which will control your modal state (open, closed) every time for every modal, and it's not convenient.

### General Consumer Modal

This is an approach just consumes `title` and `description` props and renders them in a general container.

This approach lacks of flexibility, but it's easier to use.
There also may be problems
- styling, because you can't style a modal component itself, you can only style a container
- multiple languages, because you can't apply its own translation to each modal window separately, you will have to pass it to a method, which is not convenient

## Contribute

Needs feedback, please contribute to GitHub Issues or leave your message to the [discord server](https://discord.gg/DCUWrRhvnt).