react-cool-portal
Advanced tools
Comparing version 0.0.16 to 0.1.0
@@ -18,6 +18,7 @@ declare module 'react-cool-portal' { | ||
defaultShow?: boolean; | ||
clickOutsideToHide?: boolean; | ||
escToHide?: boolean; | ||
internalShowHide?: boolean; | ||
onShow?: RCPF; | ||
onHide?: RCPF<ReactMouseEvent | MouseEvent | KeyboardEvent>; | ||
clickOutsideToHide?: boolean; | ||
escToHide?: boolean; | ||
} | ||
@@ -36,6 +37,6 @@ | ||
defaultShow, | ||
clickOutsideToHide, | ||
escToHide, | ||
onShow, | ||
onHide, | ||
clickOutsideToHide, | ||
escToHide | ||
onHide | ||
}?: Args) => Return; | ||
@@ -42,0 +43,0 @@ |
@@ -1,1 +0,1 @@ | ||
import{useState as n,useEffect as t,useRef as e,useCallback as r,useMemo as o}from"react";import{createPortal as i}from"react-dom";function u(n,t){return function(n){if(Array.isArray(n))return n}(n)||function(n,t){if(!(Symbol.iterator in Object(n)||"[object Arguments]"===Object.prototype.toString.call(n)))return;var e=[],r=!0,o=!1,i=void 0;try{for(var u,c=n[Symbol.iterator]();!(r=(u=c.next()).done)&&(e.push(u.value),!t||e.length!==t);r=!0);}catch(n){o=!0,i=n}finally{try{r||null==c.return||c.return()}finally{if(o)throw i}}return e}(n,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}var c=function(n){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:100,e=setTimeout((function(){clearTimeout(e),n()}),t)},a=function(e,r,o,a){return function(d){var f=d.children,l=u(n(null),2),v=l[0],m=l[1];return t((function(){return m(document.getElementById(e)||function(n){var t=document.createElement("div");return t.setAttribute("id",n),document.body.appendChild(t),t}(e)),function(){v&&c((function(){""===v.innerHTML&&v.remove()}))}}),[v]),t((function(){if((o||a)&&r&&v){var n=function(n){v.contains(n.target)||o(n)},t=function(n){27===n.keyCode&&a(n)};return o&&document.addEventListener("click",n),a&&document.addEventListener("keydown",t),function(){o&&document.removeEventListener("click",n),a&&document.removeEventListener("keydown",t)}}}),[v]),r&&v&&i(f,v)}};export default function(){var i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},d=i.containerId,f=void 0===d?"react-cool-portal":d,l=i.defaultShow,v=void 0===l||l,m=i.onShow,s=i.onHide,y=i.clickOutsideToHide,h=void 0===y||y,p=i.escToHide,g=void 0===p||p,w=n(v),b=u(w,2),E=b[0],k=b[1],S=e(!1),T=e(null),L=e(null);t((function(){m&&(T.current=m)}),[m]),t((function(){s&&(L.current=s)}),[s]);var A=r((function(){h&&E&&(S.current=!0,c((function(){S.current=!1})))}),[h,E]),H=r((function(n){A(),E||(k(!0),m&&m(n))}),[A,E,m]),j=r((function(n){A(),E&&(k(!1),s&&s(n))}),[A,E,s]),I=r((function(n){E?j(n):H(n)}),[E,j,H]),O=r((function(n){S.current||j(n)}),[j]),x=o((function(){return a(f,E,h&&O,g&&O)}),[f,E,h,g,O]);return{Portal:x,isShow:E,show:H,hide:j,toggle:I}} | ||
import{useState as n,useEffect as t,useRef as e,useCallback as r,useMemo as o}from"react";import{createPortal as i}from"react-dom";function u(n,t){return function(n){if(Array.isArray(n))return n}(n)||function(n,t){if(!(Symbol.iterator in Object(n)||"[object Arguments]"===Object.prototype.toString.call(n)))return;var e=[],r=!0,o=!1,i=void 0;try{for(var u,c=n[Symbol.iterator]();!(r=(u=c.next()).done)&&(e.push(u.value),!t||e.length!==t);r=!0);}catch(n){o=!0,i=n}finally{try{r||null==c.return||c.return()}finally{if(o)throw i}}return e}(n,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}var c=function(n){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:100,e=setTimeout((function(){clearTimeout(e),n()}),t)},d=function(e,r,o,d){return function(a){var l=a.children,f=u(n(null),2),v=f[0],m=f[1];return t((function(){return m(document.getElementById(e)||function(n){var t=document.createElement("div");return t.setAttribute("id",n),document.body.appendChild(t),t}(e)),function(){v&&c((function(){""===v.innerHTML&&v.remove()}))}}),[v]),t((function(){if((o||d)&&r&&v){var n=function(n){v.contains(n.target)||o(n)},t=function(n){27===n.keyCode&&d(n)};return o&&document.addEventListener("click",n),d&&document.addEventListener("keydown",t),function(){o&&document.removeEventListener("click",n),d&&document.removeEventListener("keydown",t)}}}),[v]),r&&v&&i(l,v)}};export default function(){var i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},a=i.containerId,l=void 0===a?"react-cool-portal":a,f=i.defaultShow,v=void 0===f||f,m=i.clickOutsideToHide,s=void 0===m||m,h=i.escToHide,y=void 0===h||h,p=i.internalShowHide,w=void 0===p||p,g=i.onShow,b=i.onHide,E=n(v),S=u(E,2),k=S[0],T=S[1],H=e(!1),L=e(null),A=e(null);t((function(){g&&(L.current=g)}),[g]),t((function(){b&&(A.current=b)}),[b]);var j=r((function(){s&&k&&(H.current=!0,c((function(){H.current=!1})))}),[s,k]),I=r((function(n){j(),k||(T(!0),g&&g(n))}),[j,k,g]),O=r((function(n){j(),k&&(T(!1),b&&b(n))}),[j,k,b]),x=r((function(n){k?O(n):I(n)}),[k,O,I]),C=r((function(n){H.current||O(n)}),[O]),P=o((function(){return d(l,!w||k,s&&C,y&&C)}),[l,w,k,s,y,C]);return{Portal:P,isShow:k,show:I,hide:O,toggle:x}} |
@@ -1,1 +0,1 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react"),t=require("react-dom");function n(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){if(!(Symbol.iterator in Object(e)||"[object Arguments]"===Object.prototype.toString.call(e)))return;var n=[],r=!0,o=!1,u=void 0;try{for(var c,i=e[Symbol.iterator]();!(r=(c=i.next()).done)&&(n.push(c.value),!t||n.length!==t);r=!0);}catch(e){o=!0,u=e}finally{try{r||null==i.return||i.return()}finally{if(o)throw u}}return n}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}var r=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:100,n=setTimeout((function(){clearTimeout(n),e()}),t)},o=function(o,u,c,i){return function(a){var l=a.children,f=n(e.useState(null),2),d=f[0],s=f[1];return e.useEffect((function(){return s(document.getElementById(o)||function(e){var t=document.createElement("div");return t.setAttribute("id",e),document.body.appendChild(t),t}(o)),function(){d&&r((function(){""===d.innerHTML&&d.remove()}))}}),[d]),e.useEffect((function(){if((c||i)&&u&&d){var e=function(e){d.contains(e.target)||c(e)},t=function(e){27===e.keyCode&&i(e)};return c&&document.addEventListener("click",e),i&&document.addEventListener("keydown",t),function(){c&&document.removeEventListener("click",e),i&&document.removeEventListener("keydown",t)}}}),[d]),u&&d&&t.createPortal(l,d)}};exports.default=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},u=t.containerId,c=void 0===u?"react-cool-portal":u,i=t.defaultShow,a=void 0===i||i,l=t.onShow,f=t.onHide,d=t.clickOutsideToHide,s=void 0===d||d,v=t.escToHide,m=void 0===v||v,y=e.useState(a),b=n(y,2),h=b[0],k=b[1],p=e.useRef(!1),E=e.useRef(null),g=e.useRef(null);e.useEffect((function(){l&&(E.current=l)}),[l]),e.useEffect((function(){f&&(g.current=f)}),[f]);var w=e.useCallback((function(){s&&h&&(p.current=!0,r((function(){p.current=!1})))}),[s,h]),S=e.useCallback((function(e){w(),h||(k(!0),l&&l(e))}),[w,h,l]),C=e.useCallback((function(e){w(),h&&(k(!1),f&&f(e))}),[w,h,f]),T=e.useCallback((function(e){h?C(e):S(e)}),[h,C,S]),L=e.useCallback((function(e){p.current||C(e)}),[C]),j=e.useMemo((function(){return o(c,h,s&&L,m&&L)}),[c,h,s,m,L]);return{Portal:j,isShow:h,show:S,hide:C,toggle:T}}; | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react"),t=require("react-dom");function n(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){if(!(Symbol.iterator in Object(e)||"[object Arguments]"===Object.prototype.toString.call(e)))return;var n=[],r=!0,o=!1,u=void 0;try{for(var i,c=e[Symbol.iterator]();!(r=(i=c.next()).done)&&(n.push(i.value),!t||n.length!==t);r=!0);}catch(e){o=!0,u=e}finally{try{r||null==c.return||c.return()}finally{if(o)throw u}}return n}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}var r=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:100,n=setTimeout((function(){clearTimeout(n),e()}),t)},o=function(o,u,i,c){return function(a){var l=a.children,f=n(e.useState(null),2),d=f[0],s=f[1];return e.useEffect((function(){return s(document.getElementById(o)||function(e){var t=document.createElement("div");return t.setAttribute("id",e),document.body.appendChild(t),t}(o)),function(){d&&r((function(){""===d.innerHTML&&d.remove()}))}}),[d]),e.useEffect((function(){if((i||c)&&u&&d){var e=function(e){d.contains(e.target)||i(e)},t=function(e){27===e.keyCode&&c(e)};return i&&document.addEventListener("click",e),c&&document.addEventListener("keydown",t),function(){i&&document.removeEventListener("click",e),c&&document.removeEventListener("keydown",t)}}}),[d]),u&&d&&t.createPortal(l,d)}};exports.default=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},u=t.containerId,i=void 0===u?"react-cool-portal":u,c=t.defaultShow,a=void 0===c||c,l=t.clickOutsideToHide,f=void 0===l||l,d=t.escToHide,s=void 0===d||d,v=t.internalShowHide,m=void 0===v||v,y=t.onShow,h=t.onHide,b=e.useState(a),k=n(b,2),p=k[0],E=k[1],w=e.useRef(!1),g=e.useRef(null),S=e.useRef(null);e.useEffect((function(){y&&(g.current=y)}),[y]),e.useEffect((function(){h&&(S.current=h)}),[h]);var C=e.useCallback((function(){f&&p&&(w.current=!0,r((function(){w.current=!1})))}),[f,p]),T=e.useCallback((function(e){C(),p||(E(!0),y&&y(e))}),[C,p,y]),H=e.useCallback((function(e){C(),p&&(E(!1),h&&h(e))}),[C,p,h]),L=e.useCallback((function(e){p?H(e):T(e)}),[p,H,T]),j=e.useCallback((function(e){w.current||H(e)}),[H]),A=e.useMemo((function(){return o(i,!m||p,f&&j,s&&j)}),[i,m,p,f,s,j]);return{Portal:A,isShow:p,show:T,hide:H,toggle:L}}; |
{ | ||
"name": "react-cool-portal", | ||
"version": "0.0.16", | ||
"version": "0.1.0", | ||
"description": "React hook for Portals, which renders modals, dropdowns, tooltips etc. to <body> or else.", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
109
README.md
@@ -20,2 +20,4 @@ > ⚠️ This library is in-progress, API might changed rapidly. I don't recommend to use it now. If you'd like to give it a try, please follow the [release note](https://github.com/wellyshen/react-cool-portal/releases) for any change. Here's the [milestone](#milestone). | ||
## Live Demo | ||
 | ||
@@ -32,2 +34,3 @@ | ||
- [x] Support clickOutsideToHide and escToHide interactions | ||
- [x] Provide a flexible way to handle animations | ||
- [x] Server-side rendering compatibility | ||
@@ -57,2 +60,4 @@ - [ ] Unit testing | ||
Here are some minimal examples of how does it work. You can learn more about it by checking the [API](#api) out. | ||
### Basic Use Case | ||
@@ -81,3 +86,3 @@ | ||
By default, the children of the `Portal` is rendered into `<div id="react-cool-portal">` of `<body>`. You can use your own container element by the `containerId` option. | ||
By default, the children of portal is rendered into `<div id="react-cool-portal">` of `<body>`. You can use your own container by the `containerId` option. | ||
@@ -113,13 +118,10 @@ ```js | ||
const { Portal, isShow, show, hide, toggle } = usePortal({ | ||
containerId: 'my-portal-root', | ||
defaultShow: false, // Default is true. | ||
clickOutsideToHide: true, // Default is true. | ||
escToHide: true, // Default is true. | ||
defaultShow: false, // The default visibility of portal, default is true | ||
onShow: e => { | ||
// Triggered on show portal. | ||
// The event object will be MouseEvent, KeyboardEvent, Your custom event. Depends on your interaction. | ||
// Triggered when portal is shown | ||
// The event object will be the parameter of "show()" | ||
}, | ||
onHide: e => { | ||
// Triggered on hide portal. | ||
// The event object will be MouseEvent, KeyboardEvent, Your custom event. Depends on your interaction. | ||
// Triggered when portal is hidden | ||
// The event object will be the parameter of "hide()", MouseEvent (on clicks outside) or KeyboardEvent (press ESC key) | ||
} | ||
@@ -134,5 +136,5 @@ }); | ||
<Portal> | ||
<div class="modal-backdrop" tabIndex={-1}> | ||
<div class="modal" tabIndex={-1}> | ||
<div | ||
class="modal" | ||
class="modal-dialog" | ||
role="dialog" | ||
@@ -160,2 +162,87 @@ aria-labelledby="modal-label" | ||
The above example shows how easy you can handle the visibility of your component. You may ask how to handle the visibility with animations? No worries, you can disable the built-in `show/hide` functions by setting the `internalShowHide` option as `false` then handling the visibility of your component via the `isShow` state. | ||
```js | ||
import React from 'react'; | ||
import usePortal from 'react-cool-portal'; | ||
const App = () => { | ||
const { Portal, isShow, show, hide, toggle } = usePortal({ | ||
defaultShow: false, | ||
internalShowHide: false, // Disable the built-in show/hide portal functions, default is true | ||
onShow: e => { | ||
// Triggered when "isShow" is set as true | ||
}, | ||
onHide: e => { | ||
// Triggered when "isShow" is set as false | ||
} | ||
}); | ||
return ( | ||
<div> | ||
<button onClick={show}>Open Modal</button> | ||
<button onClick={hide}>Close Modal</button> | ||
<button onClick={toggle}>{isShow ? 'Close' : 'Open'} Modal</button> | ||
<Portal> | ||
<div | ||
// Now you can use the "isShow" state to handle the CSS animations | ||
class={`modal${isShow ? ' modal-open' : ''}`} | ||
tabIndex={-1} | ||
> | ||
<div | ||
class="modal-dialog" | ||
role="dialog" | ||
aria-labelledby="modal-label" | ||
aria-modal="true" | ||
> | ||
<div class="modal-header"> | ||
<h5 id="modal-label" class="modal-title"> | ||
Modal title | ||
</h5> | ||
</div> | ||
<div class="modal-body"> | ||
<p>Modal body text goes here.</p> | ||
</div> | ||
</div> | ||
</div> | ||
</Portal> | ||
</div> | ||
); | ||
}; | ||
``` | ||
Besides that, you can also handle the visibility of your component via React [animation events](https://reactjs.org/docs/events.html#animation-events) or [translation events](https://reactjs.org/docs/events.html#transition-events) like [what I did](https://github.com/wellyshen/react-cool-portal/blob/master/demo/App/index.tsx) for the [demo app](#live-demo). | ||
## API | ||
```js | ||
const return = usePortal(parameter); | ||
``` | ||
### Return object | ||
It's returned with the following properties. | ||
| Key | Type | Default | Description | | ||
| ------ | --------- | ------- | --------------------------------------------------------------------------------------------- | | ||
| Portal | component | | Element(s) wrapped by the component will be rendered outside the DOM hierarchy of its parent. | | ||
| isShow | boolean | `false` | The show/hide state of portal. | | ||
| show | function | | To show the portal or set the `isShow` as `true`. | | ||
| hide | function | | To hide the portal or set the `isShow` as `false`. | | ||
| toggle | function | | To toggle (show/hide) the portal or set the `isShow` as `true/false`. | | ||
### Parameter object (optional) | ||
When use `react-cool-portal` you can configure the following options via the parameter. | ||
| Key | Type | Default | Description | | ||
| ------------------ | -------- | ------------------- | --------------------------------------------------------------------------------------------------------------- | | ||
| containerId | string | `react-cool-portal` | You can use your own portal container by setting it as the id of the DOM element. | | ||
| defaultShow | boolean | `true` | The initial show/hide state of the portal. | | ||
| clickOutsideToHide | boolean | `true` | Hide the portal by clicking outside of it. | | ||
| escToHide | boolean | `true` | Hide the portal by pressing ESC key. | | ||
| internalShowHide | boolean | `true` | Enable/disable the built-in `show/hide` portal functions, which gives you a flexible way to handle your portal. | | ||
| onShow | function | | Triggered when portal is shown or the `isShow` set to `true`. | | ||
| onHide | function | | Triggered when portal is hidden or the `isShow` set to `false`. | | ||
## Contributors ✨ | ||
@@ -162,0 +249,0 @@ |
25551
56
258