├── .eslintignore ├── useModal.gif ├── src ├── index.ts ├── UseModalContext.ts ├── Provider.tsx ├── utils.ts └── useModal.tsx ├── tsconfig.json ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── useModal.test.tsx ├── .circleci └── config.yml ├── jest.config.js ├── license.md ├── .eslintrc.json ├── .gitignore ├── package.json └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist 3 | -------------------------------------------------------------------------------- /useModal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamthesiz/use-react-modal/HEAD/useModal.gif -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Provider' 2 | export * from './UseModalContext' 3 | export * from './useModal' 4 | export { default } from './useModal' -------------------------------------------------------------------------------- /src/UseModalContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react' 2 | 3 | export const FetchContext = createContext({ 4 | background: '' 5 | }) 6 | 7 | export default FetchContext 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ "es2017", "dom" ], 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "target": "es5", 7 | "strict": true, 8 | "outDir": "dist", 9 | "declaration": true, 10 | "sourceMap": true, 11 | "jsx": "react", 12 | "esModuleInterop": true, 13 | "types": [ 14 | "jest" 15 | ] 16 | }, 17 | "include": [ 18 | "src/*.ts" 19 | ], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /src/Provider.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, ReactElement, ReactChildren } from 'react' 2 | import UseModalContext from './UseModalContext' 3 | 4 | export const Provider = ({ 5 | background, 6 | // animations, // in the future 😘 7 | children, 8 | }: any): ReactElement => { 9 | const defaults = useMemo( 10 | (): any => ({ 11 | background: background || '', 12 | }), 13 | [background], 14 | ) 15 | 16 | return ( 17 | {children} 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | export function parseCSSText(cssText: string) { 3 | const cssTxt = cssText.replace(/\/\*(.|\s)*?\*\//g, " ").replace(/\s+/g, " ") 4 | const style: any = {} 5 | const rule = (cssTxt.match(/ ?(.*?) ?{([^}]*)}/) || [])[2] || cssTxt 6 | const cssToJs = (s: string) => s.replace(/\W+\w/g, match => match.slice(-1).toUpperCase()) 7 | const properties = rule.split(";").map(o => o.split(":").map(x => x && x.trim())) 8 | // eslint-disable-next-line no-restricted-syntax 9 | for (const [property, value] of properties) style[cssToJs(property)] = value 10 | return style 11 | } 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: alex-cory 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **⚠️ Make a Codesandbox ⚠️** 14 | Please do this to easily reproduce the bug. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | -------------------------------------------------------------------------------- /useModal.test.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, ReactElement } from 'react' 2 | import useModal, { Provider } from './src' 3 | import { renderHook } from '@testing-library/react-hooks' 4 | 5 | describe('useModal', () => { 6 | it('should not be open', () => { 7 | const wrapper = ({ children }: { children?: ReactNode }): ReactElement => { 8 | return {children as ReactElement} 9 | } 10 | expect(useModal).toBeDefined() 11 | const { result } = renderHook(() => useModal(), { 12 | wrapper 13 | }) 14 | const { isOpen } = result.current 15 | expect(isOpen).toBe(false) 16 | }) 17 | }) -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | test: 4 | docker: 5 | - image: circleci/node:8.10 6 | environment: 7 | - NODE_ENV: test 8 | working_directory: ~/use-react-modal 9 | steps: 10 | - checkout 11 | - restore_cache: 12 | key: use-react-modal-yarn-{{ checksum "yarn.lock" }} 13 | - run: 14 | name: Yarn Install 15 | command: | 16 | yarn install 17 | - save_cache: 18 | key: use-react-modal-yarn-{{ checksum "yarn.lock" }} 19 | paths: 20 | - ~/use-react-modal/node_modules 21 | - run: 22 | name: Run JS Tests 23 | command: yarn test 24 | workflows: 25 | version: 2 26 | build_and_test: 27 | jobs: 28 | - test 29 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | 3 | module.exports = { 4 | rootDir: process.cwd(), 5 | coverageDirectory: '/.coverage', 6 | globals: { 7 | __DEV__: true, 8 | }, 9 | collectCoverageFrom: [ 10 | 'src/**/*.{js,jsx,ts,tsx}', 11 | '!src/**/*.d.ts', 12 | '!src/**/*.test.*', 13 | '!src/test/**/*.*', 14 | ], 15 | // setupFilesAfterEnv: [path.join(__dirname, './setupTests.ts')], 16 | testMatch: [ 17 | '/**/?(*.)(spec|test).ts?(x)', 18 | ], 19 | testEnvironment: 'node', 20 | testURL: 'http://localhost', 21 | transform: { 22 | '^.+\\.(ts|tsx)$': 'ts-jest', 23 | }, 24 | transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'], 25 | // testPathIgnorePatterns: ['/src/__tests__/test-utils.tsx'], 26 | moduleNameMapper: { 27 | '^react-native$': 'react-native-web', 28 | }, 29 | moduleFileExtensions: [ 30 | 'web.js', 31 | 'js', 32 | 'json', 33 | 'web.jsx', 34 | 'jsx', 35 | 'ts', 36 | 'tsx', 37 | 'feature', 38 | 'csv', 39 | ], 40 | } 41 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alex Cory 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": [ 4 | "@typescript-eslint", 5 | "react", 6 | "react-hooks", 7 | "jest", 8 | "jest-formatting" 9 | ], 10 | "parserOptions": { 11 | "ecmaVersion": 2018, 12 | "sourceType": "module", 13 | "ecmaFeatures": { 14 | "jsx": true 15 | } 16 | }, 17 | "extends": [ 18 | "plugin:@typescript-eslint/recommended", 19 | "plugin:react/recommended", 20 | "plugin:jest/recommended", 21 | "prettier/@typescript-eslint", 22 | "plugin:prettier/recommended" 23 | ], 24 | "rules": { 25 | "@typescript-eslint/member-delimiter-style": [ 26 | "error", 27 | { 28 | "multiline": { 29 | "delimiter": "none", 30 | "requireLast": false 31 | }, 32 | "singleline": { 33 | "delimiter": "comma", 34 | "requireLast": false 35 | } 36 | } 37 | ], 38 | "@typescript-eslint/explicit-member-accessibility": ["off"], 39 | "@typescript-eslint/explicit-function-return-type": ["off"], 40 | "@typescript-eslint/no-explicit-any": ["off"], 41 | "react-hooks/rules-of-hooks": "error", 42 | "react-hooks/exhaustive-deps": "warn", 43 | "jest/consistent-test-it": [ 44 | "error", 45 | { 46 | "fn": "it" 47 | } 48 | ], 49 | "jest-formatting/padding-before-test-blocks": 2, 50 | "jest-formatting/padding-before-describe-blocks": 2, 51 | "react/prop-types": 0, 52 | "prefer-const": "warn", 53 | "no-var": "warn", 54 | "prettier/prettier": [ 55 | "error", 56 | { 57 | "printWidth": 80, 58 | "singleQuote": true, 59 | "semi": false, 60 | "tabWidth": 2, 61 | "trailingComma": "all" 62 | } 63 | ] 64 | }, 65 | "settings": { 66 | "react": { 67 | "version": "detect" 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # next.js build output 63 | .next 64 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 65 | 66 | # dependencies 67 | /node_modules 68 | /.pnp 69 | .pnp.js 70 | 71 | # testing 72 | /coverage 73 | 74 | # production 75 | /build 76 | 77 | # misc 78 | .DS_Store 79 | .env.local 80 | .env.development.local 81 | .env.test.local 82 | .env.production.local 83 | 84 | npm-debug.log* 85 | yarn-debug.log* 86 | yarn-error.log* 87 | 88 | # Parcel 89 | .cache 90 | 91 | # dependencies 92 | /node_modules 93 | /.pnp 94 | .pnp.js 95 | 96 | # testing 97 | /coverage 98 | 99 | # production 100 | /dist 101 | 102 | # misc 103 | .DS_Store 104 | .env.local 105 | .env.development.local 106 | .env.test.local 107 | .env.production.local 108 | 109 | npm-debug.log* 110 | yarn-debug.log* 111 | yarn-error.log* 112 | 113 | # Using Yarn 114 | package-lock.json 115 | 116 | # Gitkeep files 117 | !.gitkeep 118 | 119 | # IDE config 120 | .idea 121 | *.iml 122 | /venv 123 | .vscode 124 | 125 | -------------------------------------------------------------------------------- /src/useModal.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useCallback, useContext, MutableRefObject } from 'react' 2 | import UseModalContext from './UseModalContext' 3 | import usePortal from 'react-useportal' 4 | import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock' 5 | import { parseCSSText } from './utils' 6 | import useSSR from 'use-ssr' 7 | 8 | type UseModalArgs = { 9 | onOpen: any, 10 | onClose: any, 11 | background: string, 12 | } 13 | 14 | const defaults = { 15 | onOpen() {}, 16 | onClose() {}, 17 | background: '' 18 | } 19 | 20 | const modalStyles = ` 21 | position: fixed; 22 | left: 50%; 23 | top: 50%; 24 | transform: translate(-50%,-50%); 25 | z-index: 1000; 26 | ` 27 | 28 | 29 | export const useModal = ({ onOpen, onClose, background, ...config }: UseModalArgs = defaults) => { 30 | const { isServer } = useSSR() 31 | const context = useContext(UseModalContext) 32 | const bg = background === null ? '' : background || context.background 33 | 34 | const modal = useRef() as MutableRefObject 35 | 36 | const { isOpen, togglePortal, openPortal, closePortal, Portal: Backdrop, ref, portalRef } = usePortal({ 37 | onOpen(event) { 38 | if (isServer) return 39 | disableBodyScroll(document.body) 40 | 41 | // eslint-disable-next-line no-param-reassign 42 | event.portal.current.style.cssText = ` 43 | position: absolute; 44 | background: ${bg ? bg : 'transparent'}; 45 | width: 100vw; 46 | height: 100vh; 47 | top: ${window.scrollY}px; 48 | left: 0; 49 | z-index: 1000; 50 | ` 51 | 52 | if (onOpen) onOpen(event) 53 | }, 54 | onClose(event) { 55 | if (isServer) return 56 | enableBodyScroll(document.body) 57 | 58 | // eslint-disable-next-line no-param-reassign 59 | event.portal.current.removeAttribute('style') 60 | if (onClose) onClose(event) 61 | }, 62 | onPortalClick({ target }) { 63 | const clickingOutsideModal = modal && modal.current && !modal.current.contains(target as Node) 64 | if (clickingOutsideModal) closePortal() 65 | }, 66 | ...config 67 | }) 68 | 69 | const ModalWithBackdrop = useCallback((props: any) => ( 70 | 71 |
72 | 73 | ), [modalStyles]) 74 | 75 | // you cannot spread in this because it will give different values for ModalWithBackdrop 76 | // when doing array vs object destructuring 77 | return Object.assign([openPortal, closePortal, isOpen, ModalWithBackdrop, togglePortal, ref, portalRef, modal], { 78 | Modal: ModalWithBackdrop, 79 | toggleModal: togglePortal, 80 | openModal: openPortal, 81 | closeModal: closePortal, 82 | isOpen, 83 | targetRef: ref, 84 | backdropRef: portalRef, 85 | modalRef: modal, 86 | }) 87 | } 88 | 89 | export default useModal 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-react-modal", 3 | "version": "0.0.8", 4 | "homepage": "https://codesandbox.io/s/w6jp7z4pkk", 5 | "main": "dist/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/alex-cory/use-react-modal.git" 9 | }, 10 | "description": "🖼 React hook for Modals", 11 | "author": "Alex Cory ", 12 | "license": "MIT", 13 | "private": false, 14 | "scripts": { 15 | "prepublishOnly": "yarn build # runs before publish", 16 | "build": "rm -rf dist && ./node_modules/.bin/tsc --module CommonJS", 17 | "build:watch": "rm -rf dist && ./node_modules/.bin/tsc -w --module CommonJS", 18 | "test:browser": "yarn tsc && jest --env=jsdom", 19 | "test:browser:watch": "yarn tsc && jest --watch --env=jsdom", 20 | "test:server": "yarn tsc && jest --env=node", 21 | "test:server:watch": "yarn tsc && jest --watch --env=node", 22 | "test:watch": "yarn test:browser:watch && yarn test:server:watch", 23 | "test": "yarn test:browser && yarn test:server", 24 | "clean": "npm prune; yarn cache clean; rm -rf ./node_modules package-lock.json yarn.lock; yarn", 25 | "lint": "eslint ./**/*.{ts,tsx}", 26 | "lint:fix": "npm run lint -- --fix", 27 | "lint:watch": "watch 'yarn lint'" 28 | }, 29 | "peerDependencies": { 30 | "react": "^16.8.6", 31 | "react-dom": "^16.8.6" 32 | }, 33 | "dependencies": { 34 | "body-scroll-lock": "^2.6.4", 35 | "react-useportal": "^1.0.13", 36 | "use-ssr": "^1.0.19" 37 | }, 38 | "devDependencies": { 39 | "@testing-library/react-hooks": "^3.0.0", 40 | "@types/body-scroll-lock": "^2.6.1", 41 | "@types/jest": "^25.1.0", 42 | "@types/react": "^16.9.2", 43 | "@types/react-dom": "^16.8.4", 44 | "@typescript-eslint/eslint-plugin": "^2.2.0", 45 | "@typescript-eslint/parser": "^2.2.0", 46 | "eslint": "^6.3.0", 47 | "eslint-plugin-jest": "^23.0.3", 48 | "eslint-plugin-prettier": "^3.1.0", 49 | "eslint-plugin-react": "^7.14.3", 50 | "jest": "^24.7.1", 51 | "parcel-bundler": "^1.12.3", 52 | "prettier": "^1.18.2", 53 | "react": "^16.8.6", 54 | "react-dom": "^16.8.6", 55 | "react-test-renderer": "^16.8.6", 56 | "react-testing-library": "^8.0.0", 57 | "ts-jest": "^24.0.0", 58 | "typescript": "^3.4.5" 59 | }, 60 | "files": [ 61 | "dist" 62 | ], 63 | "keywords": [ 64 | "react", 65 | "hook", 66 | "use", 67 | "portal", 68 | "react-hook", 69 | "react-component", 70 | "modal", 71 | "lightbox", 72 | "tooltip", 73 | "notification", 74 | "react-portal", 75 | "react-useportal", 76 | "react-use-portal", 77 | "transportation", 78 | "react portal hook", 79 | "use-modal", 80 | "react-use-modal", 81 | "modal", 82 | "modal hook", 83 | "react-modal-hook", 84 | "dialog", 85 | "lightbox", 86 | "react-lightbox", 87 | "lightbox hook", 88 | "react-dialog-hook", 89 | "use-dialog", 90 | "use-lightbox" 91 | ] 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

useModal

3 |

4 |

🖼 React hook for Modals

5 |

6 | 7 | 8 | 9 | 10 | undefined 11 | 12 | 13 | 14 | 15 | 16 | undefined 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | undefined 28 | 29 | 30 | Known Vulnerabilities 31 | 32 | 33 | Known Vulnerabilities 34 | 35 |

36 | 37 | Simple, lightweight hook for Modals/Dialogs. 38 | 39 | This hook is also isomorphic, meaning it works with SSR (server side rendering). 40 | 41 |

42 | 43 | 44 | 45 |

46 | 47 | Features 48 | -------- 49 | - SSR (server side rendering) support 50 | - TypeScript support 51 | - 2 dependencies ([use-ssr](https://github.com/alex-cory/use-ssr), [react-useportal](https://github.com/alex-cory/react-useportal)) 52 | - Built in state 53 | 54 | ### Examples 55 | - [Example](https://codesandbox.io/s/usemodal-dj3du) 56 | 57 | Installation 58 | ------------ 59 | 60 | ```shell 61 | yarn add use-react-modal or npm i -S use-react-modal 62 | ``` 63 | 64 | Usage 65 | ----- 66 | 67 | ### Basic Usage 68 | 69 | ```jsx 70 | import useModal from 'use-react-modal' 71 | 72 | const App = () => { 73 | const { isOpen, openModal, closeModal, Modal } = useModal() 74 | 75 | return ( 76 | <> 77 | 78 | {isOpen && ( 79 | 80 | 81 | Whatever you put here will be centered to the middle of the screen. 82 | 83 | ) 84 | 85 | ) 86 | } 87 | ``` 88 | 89 | ### With Provider 90 | ```jsx 91 | import useModal, { Provider } from 'use-react-modal' 92 | 93 | const MyComponent = () => { 94 | const { isOpen, openModal, Modal } = useModal() 95 | 96 | return ( 97 | <> 98 | 99 | {isOpen && ( 100 | 101 | Now, whatever you put here will be centered AND have a backdrop 102 | with the color specified in the Provider 103 | 104 | ) 105 | 106 | ) 107 | } 108 | 109 | 110 | const App = () => ( 111 | 112 | 113 | 114 | ) 115 | ``` 116 | 117 | **Make sure you are passing the html synthetic event to the `openModal` and `toggleModal` . i.e. `onClick={e => openModal(e)}`** 118 | 119 | ### Usage with a `ref` 120 | If for some reason, you don't want to pass around the `event` to `openModal` or `toggleModal`, you can use a `targetRef` like this. 121 | ```jsx 122 | import useModal from 'use-react-modal' 123 | 124 | const App = () => { 125 | const { targetRef, openModal, closeModal, isOpen, Modal } = useModal() 126 | 127 | return ( 128 | <> 129 | {/* see below how I don't have to pass the event if I use the ref */} 130 | 133 | {isOpen && ( 134 | 135 |

136 | , hit ESC or 137 | Cool Modal 😜 138 |

139 |
140 | )} 141 | 142 | ) 143 | } 144 | ``` 145 | 146 | Options 147 | ----- 148 | | Option | Description | 149 | | --------------------- | ---------------------------------------------------------------------------------------- | 150 | | `background` | sets the color of the backdrop, if nothing is set, there will be no backdrop | 151 | | `closeOnOutsideClick` | This will close the modal when not clicking within the modal. Default is `true` | 152 | | `closeOnEsc` | This will allow you to hit ESC and it will close the modal. Default is `true` | 153 | | `bindTo` | This is the DOM node you want to attach the modal to. By default it attaches to `document.body` | 154 | | `isOpen` | This will be the default for the modal being open or closed. Default is `false` | 155 | | `onOpen` | This is used to call something when the modal is opened | 156 | | `onClose` | This is used to call something when the modal is closed | 157 | | html event handlers (i.e. `onClick`) | These can be used instead of `onOpen`. | 158 | 159 | ### Option Usage 160 | 161 | ```js 162 | const { 163 | openModal, 164 | closeModal, 165 | toggleModal, 166 | isOpen, 167 | Modal, 168 | // if you don't pass an event to openModal, closeModal, or toggleModal, you will need to 169 | // put this on the element you want to interact with/click to open the modal 170 | targetRef, 171 | // this allows you to interact directly with the backdrop/overlay 172 | backdropRef, 173 | // this allows you to interact directly with the modal 174 | modalRef, 175 | } = useModal({ 176 | // sets the color of the backdrop, if nothing is set, the backdrop will be transparent unless it's set in the Provider 177 | // setting to `null` removes any background set in the `Provider` 178 | background: 'rgba(0, 0, 0, 0.5)', 179 | closeOnOutsideClick: true, 180 | closeOnEsc: true, 181 | bindTo, // attach the portal to this node in the DOM 182 | isOpen: false, 183 | // `event` has all the fields that a normal `event` would have such as `event.target.value`, etc. 184 | // with the additional `portal` and `targetEl` added to it as seen in the examples below 185 | onOpen: (event) => { 186 | // can access: event.portal, event.targetEl, event.event, event.target, etc. 187 | }, 188 | // `onClose` will not have an `event` unless you pass an `event` to `closePortal` 189 | onClose({ targetEl, event, portal }) {}, 190 | // `targetEl` is the element that you either are attaching a `ref` to 191 | // or that you are putting `openPortal` or `togglePortal` or `closePortal` on 192 | 193 | // in addition, any event handler such as onClick, onMouseOver, etc will be handled the same 194 | onClick({ targetEl, event, portal }) {} 195 | }) 196 | ``` 197 | Can also do array destructuring 198 | ```js 199 | const [openModal, closeModal, isOpen, Modal, toggleModal, targetRef, portalRef, modalRef] = useModal() 200 | ``` 201 | 202 | Todos 203 | ------ 204 | - [ ] animations 😜 205 | - [ ] React Native support. [1](https://github.com/zenyr/react-native-portal) [2](https://github.com/cloudflare/react-gateway) [3](https://medium.com/@naorzruk/portals-in-react-native-22797ba8aa1b) [4](https://stackoverflow.com/questions/46505378/can-we-have-react-16-portal-functionality-react-native) [5](https://github.com/callstack/react-native-paper/blob/master/src/components/Portal/PortalManager.tsx) Probably going to have to add a `Provider`... 206 | - [ ] add correct return types 207 | - [ ] tests (priority) 208 | - [ ] potential syntax ideas 209 | ``` 210 | // then you can change the order of the array destructuring syntax 211 | 212 | 213 | // CustomModal, CustomCloseButton 214 | 215 | const { Modal, CloseButton } = useProvider() 216 | 217 | // customize the modal animations 218 | const { Modal } = useModal({ 219 | onOpen({ modal }) { 220 | modal.current.style.cssText = ` 221 | /* do some animation in */ 222 | ` 223 | }, 224 | onClose({ modal }) { 225 | modal.current.style.cssText = ` 226 | /* do some animation out */ 227 | ` 228 | } 229 | }) 230 | 231 | // customize the modal animations idea 2 232 | const { Modal } = useModal({ 233 | animateIn: ` 234 | /* css for animating in */ 235 | `, 236 | animateOut: ` 237 | /* css for animating out */ 238 | `, 239 | }) 240 | 241 | // customize the modal animations idea 3 242 | // maybe have some predefined options? 243 | const { Modal } = useModal({ 244 | animate: 'fade-in-out', // 'slide-in-top', etc... 245 | }) 246 | 247 | // check out http://reactcommunity.org/react-modal/styles/transitions.html 248 | ``` 249 | --------------------------------------------------------------------------------