react-modal-global
Advanced tools
Comparing version 0.9.33 to 1.0.0
@@ -1,4 +0,5 @@ | ||
import { ReactElement, Component, ReactNode } from 'react'; | ||
import { ReactElement, Component, ReactNode, ComponentLifecycle } from 'react'; | ||
import { HasRequiredKeys } from 'type-fest'; | ||
declare type ModalComponent<P = unknown> = (props: P) => ReactElement; | ||
type ModalComponent<P = unknown> = (props: P) => ReactElement; | ||
interface ModalParams { | ||
@@ -35,16 +36,94 @@ /** | ||
} | ||
/** | ||
* Gets either a tuple with required or optional parameters depending on whether `P` has any required keys. | ||
* | ||
* Can be used in function argument to make `params` optional if `P` has only optional keys. | ||
* | ||
* @example | ||
* function open<P>(component: ModalComponent<P>, ...[modalParams]: ModalWindowParams<P>) {} | ||
* | ||
* const OkComponent = () => <div /> | ||
* open(OkComponent, { id: 1 }) // OK | ||
* open(OkComponent) // OK | ||
* | ||
* const FailComponent = (props: { required: boolean }) => <div /> | ||
* open(FailComponent, { required: true }) // OK | ||
* open(FailComponent) // Error: missing required property `required` | ||
*/ | ||
type ModalWindowParams<P = unknown> = HasRequiredKeys<NonNullable<P>> extends true ? [Partial<ModalParams> & P] : [(Partial<ModalParams> & P)?]; | ||
declare class Modal { | ||
static open<P>(component: ModalComponent<P>, ...[modalParams]: keyof P extends never ? [Partial<ModalParams>?] : [Partial<ModalParams> & P]): ModalWindow<P> & PromiseLike<void>; | ||
static replace<P>(component: ModalComponent<P>, ...[params]: keyof P extends never ? [Partial<ModalParams>?] : [Partial<ModalParams> & P]): ModalWindow<P> & PromiseLike<void>; | ||
private static add; | ||
private static remove; | ||
private static fork; | ||
static closeAll(): void; | ||
/** | ||
* Controller for opening and closing modal windows. | ||
* | ||
* Can be used with `ModalContainer` or with custom implementation. | ||
*/ | ||
declare class ModalController { | ||
/** | ||
* Opens a modal window. Infers props from the component. | ||
* | ||
* - If the same modal window is already in the queue, it will be ignored. | ||
* - If the modal window is weak, it will be unmounted after closing. | ||
* - If the modal window is forked, it will be opened over all other modals. | ||
* - If the modal window is closable (be default `true`), it will be closed by clicking on the overlay. | ||
* - If the modal window is not closable, it will be closed only by calling internal `close` method. | ||
* | ||
* @param component Modal component. | ||
* @param params Modal params. | ||
* @returns Modal window and `PromiseLike`. | ||
* | ||
* @example | ||
* const modal = modalController.open(MyModal, { id: 1 }) | ||
* modal.closable // `true` by default | ||
* | ||
* modal.then(() => console.log("Modal was closed")) | ||
* modal.close() | ||
*/ | ||
open<P>(component: ModalComponent<P>, ...[modalParams]: ModalWindowParams<P>): ModalWindow<P> & PromiseLike<void>; | ||
/** | ||
* Replaces the last modal window in the queue with a new one. | ||
* | ||
* If the queue is empty, it will be added to the queue. | ||
*/ | ||
replace<P>(component: ModalComponent<P>, ...[params]: ModalWindowParams<P>): ModalWindow<P> & PromiseLike<void>; | ||
/** | ||
* Adds a modal window to the queue. | ||
*/ | ||
private add; | ||
/** | ||
* Removes a modal window from the queue. | ||
*/ | ||
private remove; | ||
/** | ||
* Forks a modal window and adds it to a forked queue. | ||
* | ||
* It means that the modal will be open over all other modals. | ||
*/ | ||
private fork; | ||
/** | ||
* Closes all modals by its component (including forked) starting from the last one. | ||
*/ | ||
closeByComponent<P>(component: ModalComponent<P>): void; | ||
/** | ||
* Closes all modals by its id (including forked) starting from the last one. | ||
*/ | ||
closeById(id: ModalParams["id"]): void; | ||
/** | ||
* Closes all modals (including forked). | ||
*/ | ||
closeAll(): void; | ||
} | ||
declare const Modal: ModalController; | ||
interface ModalContainerProps { | ||
/** | ||
* Template for modal window. | ||
*/ | ||
template?: (props: { | ||
children: ReactNode; | ||
}) => ReactElement; | ||
/** | ||
* Modal container class name. It will be used as a base for modifiers (will replace defaulted `"modal"`). | ||
* | ||
* @default "modal" | ||
*/ | ||
className?: string; | ||
@@ -57,2 +136,7 @@ } | ||
} | ||
/** | ||
* Modal container component. Renders modal windows. | ||
* | ||
* Can be used multiple times to render modals in different places. | ||
*/ | ||
declare class ModalContainer extends Component<ModalContainerProps, ModalContainerState> { | ||
@@ -67,4 +151,13 @@ state: ModalContainerState; | ||
declare function useModalContext(): ModalWindow<unknown>; | ||
/** | ||
* Used inside a modal component to access the modal context (`ModalWindow`). | ||
* | ||
* Accepts a generic type that is used to infer the props of the modal component. | ||
* It has 3 overloads: | ||
* 1. `useModalContext<ModalComponent>()` - infers the props from the class component type. | ||
* 2. `useModalContext<typeof ModalComponent>()` - infers the props from the function component type. | ||
* 3. `useModalContext<unknown>()` - infers any type besides the above. | ||
*/ | ||
declare function useModalContext<T>(): ModalWindow<T extends ComponentLifecycle<infer P, unknown> | ((props: infer P) => ReactNode) ? P : T>; | ||
export { Modal, ModalContainer, useModalContext }; | ||
export { Modal, ModalContainer, ModalController, useModalContext }; |
@@ -1,14 +0,16 @@ | ||
'use strict';Object.defineProperty(exports,"__esModule",{value:!0});var jsxRuntime=require("react/jsx-runtime"),react=require("react"),extendStatics=function(d,b){extendStatics=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(a,c){a.__proto__=c}||function(a,c){for(var e in c)Object.prototype.hasOwnProperty.call(c,e)&&(a[e]=c[e])};return extendStatics(d,b)}; | ||
function __extends(d,b){function a(){this.constructor=d}if("function"!==typeof b&&null!==b)throw new TypeError("Class extends value "+String(b)+" is not a constructor or null");extendStatics(d,b);d.prototype=null===b?Object.create(b):(a.prototype=b.prototype,new a)}var __assign=function(){__assign=Object.assign||function(d){for(var b,a=1,c=arguments.length;a<c;a++){b=arguments[a];for(var e in b)Object.prototype.hasOwnProperty.call(b,e)&&(d[e]=b[e])}return d};return __assign.apply(this,arguments)}; | ||
function __read(d,b){var a="function"===typeof Symbol&&d[Symbol.iterator];if(!a)return d;d=a.call(d);var c,e=[];try{for(;(void 0===b||0<b--)&&!(c=d.next()).done;)e.push(c.value)}catch(g){var f={error:g}}finally{try{c&&!c.done&&(a=d["return"])&&a.call(d)}finally{if(f)throw f.error;}}return e}function __spreadArray(d,b,a){if(a||2===arguments.length)for(var c=0,e=b.length,f;c<e;c++)!f&&c in b||(f||(f=Array.prototype.slice.call(b,0,c)),f[c]=b[c]);return d.concat(f||Array.prototype.slice.call(b))} | ||
var modalContext=react.createContext(null);function classWithModifiers(d){for(var b=[],a=1;a<arguments.length;a++)b[a-1]=arguments[a];b=b.filter(Boolean);if(!b.length)return d;b=b.map(function(c){return d+"--"+c});return d+" "+b.join(" ")}var getCircularReplacer=function(){var d=new WeakSet;return function(b,a){if("object"===typeof a&&null!==a){if(d.has(a))return;d.add(a)}return a}};function serialize(d){return null==d?String(d):JSON.stringify(d,getCircularReplacer())} | ||
function stopPropagation(d){return function(b){var a=b.target;b=b.currentTarget;a instanceof Element&&b instanceof Element&&a!==b||(null===d||void 0===d?void 0:d())}} | ||
var containers=new Set,ModalContainer=function(d){function b(){var a=null!==d&&d.apply(this,arguments)||this;a.state={active:!1,queue:[],forkedQueue:[]};return a}__extends(b,d);Object.defineProperty(b.prototype,"className",{get:function(){return this.props.className||"modal"},enumerable:!1,configurable:!0});b.prototype.componentDidMount=function(){containers.add(this)};b.prototype.componentWillUnmount=function(){containers.delete(this)};b.prototype.render=function(){var a,c,e=this.state,f=e.active; | ||
e=e.queue;e=e[e.length-1];var g=(null===(a=null===e||void 0===e?void 0:e.params)||void 0===a?0:a.closable)?stopPropagation(e.close):void 0;a=this.props.template||react.Fragment;return jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsx("div",__assign({className:classWithModifiers(this.className,f&&"active"),"aria-modal":!0,"aria-hidden":!f},{children:jsxRuntime.jsx("div",__assign({className:this.className+"__container",onClick:g},{children:jsxRuntime.jsx(modalContext.Provider,__assign({value:e|| | ||
null},{children:jsxRuntime.jsx(a,{children:(null===e||void 0===e?void 0:e.component)&&react.createElement(e.component,__assign({},e.params,{key:null===(c=null===e||void 0===e?void 0:e.params)||void 0===c?void 0:c.id}))})}))}))})),this.renderForks()]})};b.prototype.renderForks=function(){var a=this;return this.state.forkedQueue.map(function(c){var e;return jsxRuntime.jsx("div",__assign({className:classWithModifiers(a.className,"active"),"aria-modal":!0},{children:jsxRuntime.jsx("div",__assign({className:a.className+ | ||
"__container",onClick:(null===(e=c.params)||void 0===e?0:e.closable)?stopPropagation(c.close):void 0},{children:jsxRuntime.jsx(modalContext.Provider,__assign({value:c},{children:jsxRuntime.jsx(c.component,__assign({},c.params))}))}))}),c.params.id)})};return b}(react.Component),DEFAULT_STATE={active:!1,queue:[],forkedQueue:[]},DEFAULT_PARAMS={id:0,closable:!0,weak:!1,fork:!1}; | ||
function dispatch(d){var b=__spreadArray([],__read(containers),!1).at(-1);null==b?console.warn("ModalError: no containers were mounted."):b.setState(d)} | ||
var Modal=function(){function d(){}d.open=function(b){for(var a=[],c=1;c<arguments.length;c++)a[c-1]=arguments[c];a=__read(a,1)[0];a=__assign(__assign({},DEFAULT_PARAMS),a);var e={component:b,params:a,close:function(){f();d.remove(e)}},f=function(){},g=new Promise(function(h){return f=h});d.add(e);return __assign(__assign({},e),{then:function(h,k){return g.then(h,k)}})};d.replace=function(b){for(var a=[],c=1;c<arguments.length;c++)a[c-1]=arguments[c];a=__read(a,1)[0];dispatch(function(e){return __assign(__assign({}, | ||
e),{queue:e.queue.slice(0,-1)})});return d.open(b,a)};d.add=function(b){b.params.fork?this.fork(b):dispatch(function(a){var c;return(null===(c=b.params)||void 0===c||!c.weak)&&0<a.queue.length&&(c=a.queue[a.queue.length-1],serialize(c.params)===serialize(b.params)&&c.component===b.component)?__assign(__assign({},a),{active:!0,queue:[b]}):!1===a.active&&1===a.queue.length?__assign(__assign({},a),{active:!0,queue:[b]}):__assign(__assign({},a),{active:!0,queue:__spreadArray(__spreadArray([],__read(a.queue), | ||
!1),[b],!1)})})};d.remove=function(b){b.params.fork?dispatch(function(a){var c=a.forkedQueue.filter(function(e){return e!==b});return __assign(__assign({},a),{forkedQueue:c})}):dispatch(function(a){var c=a.queue.filter(function(f){return f!==b}),e=0===c.length;return!b.params.weak&&e?__assign(__assign({},a),{active:!1}):__assign(__assign({},a),{queue:c,active:!e})})};d.fork=function(b){dispatch(function(a){return __assign(__assign({},a),{forkedQueue:__spreadArray(__spreadArray([],__read(a.forkedQueue), | ||
!1),[b],!1)})})};d.closeAll=function(){dispatch(function(b){b.queue.forEach(function(a){return a.close()});return DEFAULT_STATE})};return d}();function useModalContext(){var d=react.useContext(modalContext);if(!d)throw Error("ModalError: Out of Modal context");return d}exports.Modal=Modal;exports.ModalContainer=ModalContainer;exports.useModalContext=useModalContext | ||
'use strict';Object.defineProperty(exports,"__esModule",{value:!0});var jsxRuntime=require("react/jsx-runtime"),react=require("react"),extendStatics=function(e,b){extendStatics=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(a,c){a.__proto__=c}||function(a,c){for(var d in c)Object.prototype.hasOwnProperty.call(c,d)&&(a[d]=c[d])};return extendStatics(e,b)}; | ||
function __extends(e,b){function a(){this.constructor=e}if("function"!==typeof b&&null!==b)throw new TypeError("Class extends value "+String(b)+" is not a constructor or null");extendStatics(e,b);e.prototype=null===b?Object.create(b):(a.prototype=b.prototype,new a)}var __assign=function(){__assign=Object.assign||function(e){for(var b,a=1,c=arguments.length;a<c;a++){b=arguments[a];for(var d in b)Object.prototype.hasOwnProperty.call(b,d)&&(e[d]=b[d])}return e};return __assign.apply(this,arguments)}; | ||
function __read(e,b){var a="function"===typeof Symbol&&e[Symbol.iterator];if(!a)return e;e=a.call(e);var c,d=[];try{for(;(void 0===b||0<b--)&&!(c=e.next()).done;)d.push(c.value)}catch(g){var f={error:g}}finally{try{c&&!c.done&&(a=e["return"])&&a.call(e)}finally{if(f)throw f.error;}}return d}function __spreadArray(e,b,a){if(a||2===arguments.length)for(var c=0,d=b.length,f;c<d;c++)!f&&c in b||(f||(f=Array.prototype.slice.call(b,0,c)),f[c]=b[c]);return e.concat(f||Array.prototype.slice.call(b))} | ||
var modalContext=react.createContext(null);function classWithModifiers(e){for(var b=[],a=1;a<arguments.length;a++)b[a-1]=arguments[a];b=b.filter(Boolean);if(!b.length)return e;b=b.map(function(c){return e+"--"+c});return e+" "+b.join(" ")}var getCircularReplacer=function(){var e=new WeakSet;return function(b,a){if("object"===typeof a&&null!==a){if(e.has(a))return;e.add(a)}return a}};function serialize(e){return null==e?String(e):JSON.stringify(e,getCircularReplacer())} | ||
function stopPropagation(e){return function(b){var a=b.target;b=b.currentTarget;a instanceof Element&&b instanceof Element&&a!==b||(null===e||void 0===e?void 0:e())}} | ||
var containers=new Set,ModalContainer=function(e){function b(){var a=null!==e&&e.apply(this,arguments)||this;a.state={active:!1,queue:[],forkedQueue:[]};return a}__extends(b,e);Object.defineProperty(b.prototype,"className",{get:function(){return this.props.className||"modal"},enumerable:!1,configurable:!0});b.prototype.componentDidMount=function(){containers.add(this)};b.prototype.componentWillUnmount=function(){containers.delete(this)};b.prototype.render=function(){var a,c,d=this.state,f=d.active; | ||
d=d.queue;d=d[d.length-1];var g=(null===(a=null===d||void 0===d?void 0:d.params)||void 0===a?0:a.closable)?stopPropagation(d.close):void 0;a=this.props.template||react.Fragment;return jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsx("div",__assign({className:classWithModifiers(this.className,f&&"active"),"aria-modal":!0,"aria-hidden":!f},{children:jsxRuntime.jsx("div",__assign({className:this.className+"__container",onClick:g},{children:jsxRuntime.jsx(modalContext.Provider,__assign({value:d|| | ||
null},{children:jsxRuntime.jsx(a,{children:(null===d||void 0===d?void 0:d.component)&&react.createElement(d.component,__assign({},d.params,{key:null===(c=null===d||void 0===d?void 0:d.params)||void 0===c?void 0:c.id}))})}))}))})),this.renderForks()]})};b.prototype.renderForks=function(){var a=this;return this.state.forkedQueue.map(function(c){var d;return jsxRuntime.jsx("div",__assign({className:classWithModifiers(a.className,"active"),"aria-modal":!0},{children:jsxRuntime.jsx("div",__assign({className:a.className+ | ||
"__container",onClick:(null===(d=c.params)||void 0===d?0:d.closable)?stopPropagation(c.close):void 0},{children:jsxRuntime.jsx(modalContext.Provider,__assign({value:c},{children:jsxRuntime.jsx(c.component,__assign({},c.params))}))}))}),c.params.id)})};return b}(react.Component),DEFAULT_STATE={active:!1,queue:[],forkedQueue:[]},DEFAULT_PARAMS={id:0,closable:!0,weak:!1,fork:!1}; | ||
function dispatch(e){var b=__spreadArray([],__read(containers),!1).at(-1);null==b?console.warn("ModalError: no containers were mounted."):b.setState(e)} | ||
var ModalController=function(){function e(){}e.prototype.open=function(b){for(var a=this,c=[],d=1;d<arguments.length;d++)c[d-1]=arguments[d];c=__read(c,1)[0];var f=function(){},g=new Promise(function(h){return f=h});c=__assign(__assign({},DEFAULT_PARAMS),c);var k={component:b,params:c,close:function(){a.remove(k);f()}};this.add(k);return __assign(__assign({},k),{then:function(h,l){return g.then(h,l)}})};e.prototype.replace=function(b){for(var a=[],c=1;c<arguments.length;c++)a[c-1]=arguments[c];a= | ||
__read(a,1)[0];dispatch(function(d){return __assign(__assign({},d),{queue:d.queue.slice(0,-1)})});return this.open(b,a)};e.prototype.add=function(b){b.params.fork?this.fork(b):dispatch(function(a){var c;if((null===(c=b.params)||void 0===c||!c.weak)&&0<a.queue.length){var d=a.queue[a.queue.length-1];c=serialize(b.params)===serialize(d.params);d=b.component===d.component;if(c&&d)return __assign(__assign({},a),{active:!0})}return __assign(__assign({},a),{active:!0,queue:__spreadArray(__spreadArray([], | ||
__read(a.queue),!1),[b],!1)})})};e.prototype.remove=function(b){b.params.fork?dispatch(function(a){var c=a.forkedQueue.filter(function(d){return d!==b});return __assign(__assign({},a),{forkedQueue:c})}):dispatch(function(a){var c=a.queue.filter(function(f){return f!==b}),d=0===c.length;return d&&!b.params.weak?__assign(__assign({},a),{active:!1}):__assign(__assign({},a),{queue:c,active:!d})})};e.prototype.fork=function(b){dispatch(function(a){return __assign(__assign({},a),{forkedQueue:__spreadArray(__spreadArray([], | ||
__read(a.forkedQueue),!1),[b],!1)})})};e.prototype.closeByComponent=function(b){dispatch(function(a){var c=__spreadArray([],__read(a.queue.filter(function(f){return f.component===b})),!1),d=__spreadArray([],__read(a.forkedQueue.filter(function(f){return f.component===b})),!1);c.reverse().forEach(function(f){return f.close()});d.reverse().forEach(function(f){return f.close()});return a})};e.prototype.closeById=function(b){dispatch(function(a){var c=a.queue.filter(function(f){return f.params.id===b}), | ||
d=a.forkedQueue.filter(function(f){return f.params.id===b});c.forEach(function(f){return f.close()});d.forEach(function(f){return f.close()});return a})};e.prototype.closeAll=function(){dispatch(function(b){b.queue.forEach(function(a){return a.close()});return DEFAULT_STATE})};return e}(),Modal=new ModalController;function useModalContext(){var e=react.useContext(modalContext);if(!e)throw Error("ModalError: useModalContext must be used within a modalContext");return e}exports.Modal=Modal; | ||
exports.ModalContainer=ModalContainer;exports.ModalController=ModalController;exports.useModalContext=useModalContext |
{ | ||
"name": "react-modal-global", | ||
"version": "0.9.33", | ||
"version": "1.0.0", | ||
"description": "React Modal but Global", | ||
@@ -11,4 +11,4 @@ "main": "dist/index.ts", | ||
], | ||
"dependencies": { | ||
"react": "^18.2.0" | ||
"peerDependencies": { | ||
"react": "18.x" | ||
}, | ||
@@ -18,2 +18,5 @@ "devDependencies": { | ||
"@rollup/plugin-typescript": "^8.3.3", | ||
"@testing-library/jest-dom": "^5.16.5", | ||
"@testing-library/react": "^13.4.0", | ||
"@types/jest": "^29.4.0", | ||
"@types/react": "^18.0.15", | ||
@@ -29,2 +32,3 @@ "eslint": "^8.6.0", | ||
"tslib": "^2.4.0", | ||
"type-fest": "^3.5.5", | ||
"typescript": "4.7.4" | ||
@@ -37,3 +41,4 @@ }, | ||
"help": "ncc", | ||
"test": "", | ||
"test": "react-scripts test", | ||
"test:coverage": "react-scripts test --coverage *", | ||
"post-fix": "eslint out --fix" | ||
@@ -57,3 +62,8 @@ }, | ||
}, | ||
"homepage": "https://github.com/FrameMuse/react-modal-global#readme" | ||
"homepage": "https://github.com/FrameMuse/react-modal-global#readme", | ||
"jest": { | ||
"testMatch": [ | ||
"**/?(*.)+(spec|test).ts?(x)" | ||
] | ||
} | ||
} |
173
README.md
# React Modal Global | ||
Needs feedback, please contribute in GitHub Issues or leave your message on [my discord server](https://discord.gg/DCUWrRhvnt). | ||
[![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)]() | ||
## Introduction | ||
[![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)]() | ||
This is a package that provides modal dialogs which does similar to [`react-modal`](https://www.npmjs.com/package/react-modal) except that it is accessed from _anywhere_. | ||
## Presentation | ||
## Features | ||
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** ^_^ | ||
## Contribute | ||
Needs feedback, please contribute in 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) | ||
- [Add container](#add-container) | ||
- [Show `ModalContainer` usage example](#show-modalcontainer-usage-example) | ||
- [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) | ||
- Multicontainers - for e.g. templates | ||
- Context - the data passed when openning can be accessed in the component via `useModalContext` hook | ||
- Stacking/Nesting | ||
- Forking | ||
- Data preservation | ||
- `open` method returns `Promise` | ||
- | ||
- 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 | ||
- The package uses only react as a dependency. | ||
## Usage | ||
#### The main idea | ||
Usage may seem a bit complicated but it's actually very simple, please, be patient and read all the thing through. | ||
There is a `ModalContainer` which is a container for modal components (it usually appears in `#root` element) and modal components will appear there as you open them. | ||
### Add container | ||
<details> | ||
<summary>Show `ModalContainer` usage example</summary> | ||
`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. | ||
#### Show `ModalContainer` usage example | ||
```tsx | ||
@@ -46,9 +98,2 @@ import React from "react" | ||
</details> | ||
There are other features upon this idea. | ||
## Usage | ||
Usage may seem a bit complicated, please, be patient and read all the thing throughout. | ||
### Create new Modal component | ||
@@ -79,4 +124,4 @@ | ||
<p>Content text</p> | ||
<button type="button" onClick={() => modal.close()}>My custom button to close modal</button> | ||
<button type="button" onClick={modal.close}>close</button> | ||
</> | ||
@@ -114,14 +159,2 @@ ) | ||
### Modal options | ||
You can use options when opening a modal. | ||
Available options | ||
| Option | Description | | ||
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| `id` | Specifies id of a modal (default: `Date.now()`). 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 controlled itself | | ||
| `weak` | By default, a last closed modal will not be removed and if same modal will be request to open, it will _restore_ previous modal but with `weak: true` it will not happen. | | ||
| `fork` | Creates a new layer for a single modal | | ||
### Modal Template | ||
@@ -135,3 +168,2 @@ | ||
### Modal layouts | ||
@@ -141,3 +173,3 @@ | ||
[See example here](./examples/PopupLayout) | ||
[![Edit react-modal-global](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/react-modal-global-examples-47yoil) | ||
@@ -186,1 +218,64 @@ To create your first `Popup` modal try this | ||
``` | ||
## Layout concept | ||
### Description | ||
Layout is a component that wraps modal component and allows to customize modal look and controls (close button, header, footer, etc.). | ||
Layouts are used to create various modal types (Dialog, Popup, Drawer) and to customize modal controls. | ||
For example, you can create your own `PopupLayout` to use it in your `Popup` modals. | ||
[See example here](./examples/PopupLayout) | ||
### Aria | ||
Layouts should not have `aria-modal` attribute and `role="dialog"` because they are already set in `ModalContainer` component. | ||
You should manually add `aria-labelledby` and `aria-describedby` attributes to your layout. | ||
## Modal controller | ||
### `Open` | ||
`Modal.open` is a method that opens a modal. See [usage](#modal-component-usage) for example. See [options](#modal-options) for more details. | ||
```tsx | ||
Modal.open(ModalComponent, { /* options */ }) | ||
``` | ||
### `Close` | ||
There is no `Modal.close` method because it's hard to know what exactly window to close, instead you can close a modal from inside of a modal component using `useModalContext` hook. | ||
Or using `Modal.closeBy` methods. | ||
#### `CloseByComponent` | ||
`Modal.closeByComponent` is a method that closes a modal by its component. It will close all modals that use this component. | ||
```tsx | ||
Modal.closeByComponent(ModalComponent) | ||
``` | ||
#### `CloseById` | ||
`Modal.closeById` is a method that closes a modal by its id. It will close all modals that have this id. | ||
```tsx | ||
Modal.closeById("insane-id") | ||
``` | ||
### Modal options | ||
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. | |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
25189
185
0
276
17
- Removedreact@^18.2.0