main 26164e3cfaba cached
22 files
33.8 KB
10.1k tokens
28 symbols
1 requests
Download .txt
Repository: AlexanderRichey/styled-react-modal
Branch: main
Commit: 26164e3cfaba
Files: 22
Total size: 33.8 KB

Directory structure:
gitextract_2y96oc27/

├── .babelrc
├── .circleci/
│   └── config.yml
├── .codesandbox/
│   └── ci.json
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .hound.yml
├── .npmignore
├── .nvmrc
├── .prettierrc
├── LICENSE
├── README.md
├── build/
│   ├── mjs/
│   │   └── index.mjs
│   └── umd/
│       └── index.js
├── package.json
├── rollup.config.js
├── src/
│   ├── Modal.jsx
│   ├── ModalProvider.jsx
│   ├── baseStyles.jsx
│   ├── context.jsx
│   └── index.jsx
└── tests/
    └── Modal.test.jsx

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc
================================================
{
  "presets": ["@babel/env", "@babel/react"],
  "plugins": [
    ["@babel/plugin-transform-runtime"],
    ["babel-plugin-styled-components", {
      "displayName": true,
      "ssr": false
    }]
  ]
}


================================================
FILE: .circleci/config.yml
================================================
version: 2.1

# Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects.
# See: https://circleci.com/docs/2.0/orb-intro/
orbs:
  node: circleci/node@5.0.2
  codecov: codecov/codecov@3.2.3

# Invoke jobs via workflows
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
workflows:
  test:
    jobs:
      - node/test:
          # This is the node version to use for the `cimg/node` tag
          # Relevant tags can be found on the CircleCI Developer Hub
          # https://circleci.com/developer/images/image/cimg/node
          version: "16.17"
          pkg-manager: yarn
          post-steps:
            - codecov/upload


================================================
FILE: .codesandbox/ci.json
================================================
{
  "sandboxes": ["m9jlky57y"]
}


================================================
FILE: .eslintignore
================================================
build/
node_modules/
coverage/


================================================
FILE: .eslintrc
================================================
{
  "extends": "prettier",
  "parser": "babel-eslint",
  "plugins": ["prettier", "react"]
}


================================================
FILE: .gitignore
================================================
# dependencies
/node_modules

# misc
.DS_Store
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/coverage


================================================
FILE: .hound.yml
================================================
eslint:
  enabled: true
  config_file: .eslintrc
  ignore_file: .eslintignore


================================================
FILE: .npmignore
================================================
coverage/


================================================
FILE: .nvmrc
================================================
v18.7.0


================================================
FILE: .prettierrc
================================================
{
  "printWidth": 80,
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": false,
  "trailingComma": "none",
  "bracketSpacing": true,
  "jsxBracketSameLine": false,
  "fluid": false
}


================================================
FILE: LICENSE
================================================
Copyright (c) 2022 Alexander Richey

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org>


================================================
FILE: README.md
================================================
# Styled React Modal

[![style: styled-components](https://img.shields.io/badge/style-%F0%9F%92%85%20styled--components-orange.svg?colorB=daa357&colorA=db748e)](https://github.com/styled-components/styled-components) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
 [![npm version](https://img.shields.io/npm/v/styled-react-modal.svg)](https://www.npmjs.com/package/styled-react-modal) [![npm downloads](https://img.shields.io/npm/dm/styled-react-modal.svg)](https://www.npmjs.com/package/styled-react-modal) [![CircleCI](https://dl.circleci.com/status-badge/img/gh/AlexanderRichey/styled-react-modal/tree/main.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/gh/AlexanderRichey/styled-react-modal/tree/main) [![codecov](https://codecov.io/gh/AlexanderRichey/styled-react-modal/branch/main/graph/badge.svg)](https://codecov.io/gh/AlexanderRichey/styled-react-modal)

> For support for **react <16.9**, please use **styled-react-modal@1.2.4**.

> For support for **create-react-app <5.0.0**, please import from `styled-react-modal/build/umd`.

Styled React Modal is built with styled-components. It uses the latest React 17.x features and exposes a familiar, easy to use API. It supports `beforeOpen()`, `afterOpen()`, and other lifecycle hooks so that animations can be handled easily. Unlike several other modal implementations in React, it does not pollute the DOM with excessive nodes.

[**Demo on CodeSandbox**](https://codesandbox.io/s/m9jlky57y)

## Install

```
npm i -s styled-react-modal  # or use yarn
```

## Usage

Add the `<ModalProvider>` component near the top of your application's tree.

```js
import React from 'react'
import { ModalProvider } from 'styled-react-modal'
...

export default function App() {
  return (
    <ThemeProvider theme={theme}>
      <ModalProvider>
        <FancyModalButton />
      </ModalProvider>
    </ThemeProvider>
  )
}
```

Use the `<Modal>` component.

> For instructions on how the make your modal accessible according to the [WAI-ARIA spec](https://www.w3.org/TR/wai-aria-practices/#dialog_modal), see [this CodeSandbox](https://codesandbox.io/s/styled-react-modal-a11y-9uco3?file=/src/index.js).

```js
import Modal from 'styled-react-modal'
...

const StyledModal = Modal.styled`
  width: 20rem;
  height: 20rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: ${props => props.theme.colors.white};
`

function FancyModalButton() {
  const [isOpen, setIsOpen] = useState(false)

  function toggleModal(e) {
    setIsOpen(!isOpen)
  }

  return (
    <div>
      <button onClick={toggleModal}>Click me</button>
      <StyledModal
        isOpen={isOpen}
        onBackgroundClick={toggleModal}
        onEscapeKeydown={toggleModal}>
        <span>I am a modal!</span>
        <button onClick={toggleModal}>Close me</button>
      </StyledModal>
    </div>
  )
}
```

## API

#### Top-Level Exports
- `<ModalProvider>`
- `Modal` \(Default\)
  - `Modal.styled(styles)`
  - `<Modal>`
- `<BaseModalBackground>`

<hr>

### `<ModalProvider>`

Sets the root portal where `<Modal>`s will be rendered.

**Props**

- [`backgroundComponent`] \(Component\): A styled component to be used as the default modal background. If not provided, library defaults will be used.

*Example:*

```js
import { ModalProvider } from 'styled-react-modal'

const SpecialModalBackground = styled.div`
  display: flex;
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: 30;
  opacity: ${props => props.opacity};
  background-color: green;
`

export default function App() {
  return (
    <ThemeProvider theme={theme}>
      <ModalProvider backgroundComponent={SpecialModalBackground}>
        <FancyModalButton />
      </ModalProvider>
    </ThemeProvider>
  )
}
```

### `Modal.styled(styles)`

Factory method that accepts a tagged template literal and returns a `<Modal>` component with styles included.

**Arguments**

 - `styles` \(Tagged Template Literal\): styled-components compatible css styles.

*Example:*

```js
const StyledModal = Modal.styled`
  width: 20rem;
  height: 20rem;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: ${props => props.theme.colors.white};
`
```

### `<Modal>`

Renders its children in a modal when open, nothing when not open.

**Props**

- `isOpen` \(Boolean\): A boolean that indicates whether the modal is to be open or closed.
- [`onBackgroundClick`] \(Function\): A function that is called when the modal background is clicked.
- [`onEscapeKeydown`] \(Function\): A function that is called when the escape key is pressed while the modal is open.
- [`backgroundProps`] \(Object\): A props object that is spread over the `backgroundComponent` when included.
- [`allowScroll`] \(Boolean\): When true, scrolling in the document body is not disabled when the modal is open.
- [`beforeOpen`] \(Function\): A function that is called before the modal opens. If this function returns a promise, then the modal is opened after the promise is resolved.
- [`afterOpen`] \(Function\): A function that is called after the modal opens.
- [`beforeClose`] \(Function\): A function that is called before the modal closes. If this function returns a promise, then the modal is closed after the promise is resolved.
- [`afterClose`] \(Function\): A function that is called after the modal closes.


*Example:*

```js
import Modal from 'styled-react-modal'

function FancyModalButton() {
  const [isOpen, setIsOpen] = useState(false)

  function toggleModal(e) {
    setIsOpen(!isOpen)
  }

  return (
    <div>
      <button onClick={toggleModal}>Click me</button>
      <Modal
        isOpen={isOpen}
        onBackgroundClick={toggleModal}
        onEscapeKeydown={toggleModal}>
        <span>I am a modal!</span>
        <button onClick={toggleModal}>Close me</button>
      </Modal>
    </div>
  )
}
```

### `<BaseModalBackground>`

A convenience base component for making default background styles with `<ModalProvider>`.

*Example:*

```js
const SpecialModalBackground = styled(BaseModalBackground)`
  background-color: green;
`
```


================================================
FILE: build/mjs/index.mjs
================================================
import e from"styled-components";import n,{useRef as r,useState as t,useEffect as o}from"react";import a from"react-dom";var c=e.div.withConfig({displayName:"baseStyles__BaseModalBackground"})(["display:flex;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:30;background-color:rgba(0,0,0,0.5);align-items:center;justify-content:center;"]);function l(e,n){(null==n||n>e.length)&&(n=e.length);for(var r=0,t=new Array(n);r<n;r++)t[r]=e[r];return t}function i(e,n){return function(e){if(Array.isArray(e))return e}(e)||function(e,n){var r=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=r){var t,o,a=[],c=!0,l=!1;try{for(r=r.call(e);!(c=(t=r.next()).done)&&(a.push(t.value),!n||a.length!==n);c=!0);}catch(e){l=!0,o=e}finally{try{c||null==r.return||r.return()}finally{if(l)throw o}}return a}}(e,n)||function(e,n){if(e){if("string"==typeof e)return l(e,n);var r=Object.prototype.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?l(e,n):void 0}}(e,n)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function u(){}function f(){}f.resetWarningCache=u;var p,s,d=function(){function e(e,n,r,t,o,a){if("SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"!==a){var c=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw c.name="Invariant Violation",c}}function n(){return e}e.isRequired=e;var r={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:n,element:e,elementType:e,instanceOf:n,node:e,objectOf:n,oneOf:n,oneOfType:n,shape:n,exact:n,checkPropTypes:f,resetWarningCache:u};return r.PropTypes=r,r},y=(p=function(e){e.exports=d()},p(s={exports:{}},s.exports),s.exports),m=y,b=n.createContext({}),v=b.Provider,g=b.Consumer;function h(e){var a=e.backgroundComponent,l=e.children,u=r(null),f=i(t(null),2),p=f[0],s=f[1],d=i(t(c),2),y=d[0],m=d[1];return o((function(){a&&m(a)}),[m,a]),o((function(){s(u.current)}),[s,u]),n.createElement(v,{value:{modalNode:p,BackgroundComponent:y}},l,n.createElement("div",{ref:u}))}function O(){return O=Object.assign?Object.assign.bind():function(e){for(var n=1;n<arguments.length;n++){var r=arguments[n];for(var t in r)Object.prototype.hasOwnProperty.call(r,t)&&(e[t]=r[t])}return e},O.apply(this,arguments)}function w(e,n){if(null==e)return{};var r,t,o=function(e,n){if(null==e)return{};var r,t,o={},a=Object.keys(e);for(t=0;t<a.length;t++)r=a[t],n.indexOf(r)>=0||(o[r]=e[r]);return o}(e,n);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(t=0;t<a.length;t++)r=a[t],n.indexOf(r)>=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}h.propTypes={backgroundComponent:m.oneOfType([m.element,m.object])};var C=["WrapperComponent","children","onBackgroundClick","onEscapeKeydown","allowScroll","beforeOpen","afterOpen","beforeClose","afterClose","backgroundProps","isOpen"];function k(e){for(var n=arguments.length,r=new Array(n>1?n-1:0),t=1;t<n;t++)r[t-1]=arguments[t];e&&e.apply(void 0,r)}function E(e){var c,l=e.WrapperComponent,u=e.children,f=e.onBackgroundClick,p=e.onEscapeKeydown,s=e.allowScroll,d=e.beforeOpen,y=e.afterOpen,m=e.beforeClose,b=e.afterClose,v=e.backgroundProps,h=void 0===v?{}:v,E=e.isOpen,j=w(e,C),S=r(null),T=r(null),_=r(!1),P=i(t(!1),2),x=P[0],A=P[1];function B(e){S.current===e.target&&f&&f(e)}return o((function(){function e(e){A(e),k(e?y:b)}function n(n){if(n){var r=n();"function"==typeof(null==r?void 0:r.then)?(_.current=!0,r.then((function(){e(E),_.current=!1}))):e(E)}else e(E)}x===E||_.current||n(E?d:m)}),[_,x,E,d,m,b,y]),o((function(){function e(e){"Escape"===e.key&&p&&p(e)}return x&&document.addEventListener("keydown",e),function(){document.removeEventListener("keydown",e)}}),[x,p]),o((function(){return x&&!s&&(T.current=document.body.style.overflow,document.body.style.overflow="hidden"),function(){s||(document.body.style.overflow=T.current||"")}}),[x,s]),c=l?n.createElement(l,j,u):u,n.createElement(g,null,(function(e){var r=e.modalNode,t=e.BackgroundComponent;return r&&t&&x?a.createPortal(n.createElement(t,O({},h,{onClick:B,ref:S}),c),r):null}))}E.styled=function(){for(var r=arguments.length,t=new Array(r),o=0;o<r;o++)t[o]=arguments[o];var a=t?e.div.withConfig({displayName:"Modal__wrap"}).apply(void 0,t):e.div.withConfig({displayName:"Modal__wrap"})();return function(e){return n.createElement(E,O({WrapperComponent:a},e))}},E.propTypes={WrapperComponent:m.oneOfType([m.element,m.object]),onBackgroundClick:m.func,onEscapeKeydown:m.func,allowScroll:m.bool,beforeOpen:m.func,afterOpen:m.func,beforeClose:m.func,afterClose:m.func,backgroundProps:m.object,isOpen:m.bool};export{c as BaseModalBackground,h as ModalProvider,E as default};


================================================
FILE: build/umd/index.js
================================================
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("styled-components"),require("react"),require("react-dom")):"function"==typeof define&&define.amd?define(["exports","styled-components","react","react-dom"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self)["styled-react-modal"]={},e.styled,e.React,e.ReactDOM)}(this,(function(e,n,t,r){"use strict";function o(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var a=o(n),u=o(t),l=o(r),c=a.default.div.withConfig({displayName:"baseStyles__BaseModalBackground"})(["display:flex;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:30;background-color:rgba(0,0,0,0.5);align-items:center;justify-content:center;"]);function f(e,n){(null==n||n>e.length)&&(n=e.length);for(var t=0,r=new Array(n);t<n;t++)r[t]=e[t];return r}function i(e,n){return function(e){if(Array.isArray(e))return e}(e)||function(e,n){var t=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=t){var r,o,a=[],u=!0,l=!1;try{for(t=t.call(e);!(u=(r=t.next()).done)&&(a.push(r.value),!n||a.length!==n);u=!0);}catch(e){l=!0,o=e}finally{try{u||null==t.return||t.return()}finally{if(l)throw o}}return a}}(e,n)||function(e,n){if(e){if("string"==typeof e)return f(e,n);var t=Object.prototype.toString.call(e).slice(8,-1);return"Object"===t&&e.constructor&&(t=e.constructor.name),"Map"===t||"Set"===t?Array.from(e):"Arguments"===t||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t)?f(e,n):void 0}}(e,n)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function p(){}function d(){}d.resetWarningCache=p;var s=function(){function e(e,n,t,r,o,a){if("SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"!==a){var u=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw u.name="Invariant Violation",u}}function n(){return e}e.isRequired=e;var t={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:n,element:e,elementType:e,instanceOf:n,node:e,objectOf:n,oneOf:n,oneOfType:n,shape:n,exact:n,checkPropTypes:d,resetWarningCache:p};return t.PropTypes=t,t},y=function(e,n){return e(n={exports:{}},n.exports),n.exports}((function(e){e.exports=s()})),m=u.default.createContext({}),b=m.Provider,v=m.Consumer;function g(e){var n=e.backgroundComponent,r=e.children,o=t.useRef(null),a=i(t.useState(null),2),l=a[0],f=a[1],p=i(t.useState(c),2),d=p[0],s=p[1];return t.useEffect((function(){n&&s(n)}),[s,n]),t.useEffect((function(){f(o.current)}),[f,o]),u.default.createElement(b,{value:{modalNode:l,BackgroundComponent:d}},r,u.default.createElement("div",{ref:o}))}function h(){return h=Object.assign?Object.assign.bind():function(e){for(var n=1;n<arguments.length;n++){var t=arguments[n];for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])}return e},h.apply(this,arguments)}function O(e,n){if(null==e)return{};var t,r,o=function(e,n){if(null==e)return{};var t,r,o={},a=Object.keys(e);for(r=0;r<a.length;r++)t=a[r],n.indexOf(t)>=0||(o[t]=e[t]);return o}(e,n);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r<a.length;r++)t=a[r],n.indexOf(t)>=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(o[t]=e[t])}return o}g.propTypes={backgroundComponent:y.oneOfType([y.element,y.object])};var w=["WrapperComponent","children","onBackgroundClick","onEscapeKeydown","allowScroll","beforeOpen","afterOpen","beforeClose","afterClose","backgroundProps","isOpen"];function C(e){for(var n=arguments.length,t=new Array(n>1?n-1:0),r=1;r<n;r++)t[r-1]=arguments[r];e&&e.apply(void 0,t)}function k(e){var n,r=e.WrapperComponent,o=e.children,a=e.onBackgroundClick,c=e.onEscapeKeydown,f=e.allowScroll,p=e.beforeOpen,d=e.afterOpen,s=e.beforeClose,y=e.afterClose,m=e.backgroundProps,b=void 0===m?{}:m,g=e.isOpen,k=O(e,w),E=t.useRef(null),j=t.useRef(null),S=t.useRef(!1),T=i(t.useState(!1),2),_=T[0],P=T[1];function x(e){E.current===e.target&&a&&a(e)}return t.useEffect((function(){function e(e){P(e),C(e?d:y)}function n(n){if(n){var t=n();"function"==typeof(null==t?void 0:t.then)?(S.current=!0,t.then((function(){e(g),S.current=!1}))):e(g)}else e(g)}_===g||S.current||n(g?p:s)}),[S,_,g,p,s,y,d]),t.useEffect((function(){function e(e){"Escape"===e.key&&c&&c(e)}return _&&document.addEventListener("keydown",e),function(){document.removeEventListener("keydown",e)}}),[_,c]),t.useEffect((function(){return _&&!f&&(j.current=document.body.style.overflow,document.body.style.overflow="hidden"),function(){f||(document.body.style.overflow=j.current||"")}}),[_,f]),n=r?u.default.createElement(r,k,o):o,u.default.createElement(v,null,(function(e){var t=e.modalNode,r=e.BackgroundComponent;return t&&r&&_?l.default.createPortal(u.default.createElement(r,h({},b,{onClick:x,ref:E}),n),t):null}))}k.styled=function(){for(var e=arguments.length,n=new Array(e),t=0;t<e;t++)n[t]=arguments[t];var r=n?a.default.div.withConfig({displayName:"Modal__wrap"}).apply(void 0,n):a.default.div.withConfig({displayName:"Modal__wrap"})();return function(e){return u.default.createElement(k,h({WrapperComponent:r},e))}},k.propTypes={WrapperComponent:y.oneOfType([y.element,y.object]),onBackgroundClick:y.func,onEscapeKeydown:y.func,allowScroll:y.bool,beforeOpen:y.func,afterOpen:y.func,beforeClose:y.func,afterClose:y.func,backgroundProps:y.object,isOpen:y.bool},e.BaseModalBackground=c,e.ModalProvider=g,e.default=k,Object.defineProperty(e,"__esModule",{value:!0})}));


================================================
FILE: package.json
================================================
{
  "name": "styled-react-modal",
  "version": "3.1.2",
  "description": "A React modal built with styled-components.",
  "main": "build/umd/index.js",
  "module": "build/mjs/index.mjs",
  "scripts": {
    "test": "jest --coverage --verbose",
    "lint": "prettier src/ tests/",
    "prepublish": "yarn run build",
    "build": "rollup -c rollup.config.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/AlexanderRichey/styled-react-modal.git"
  },
  "keywords": [
    "react-modal",
    "styled-components",
    "modal",
    "react"
  ],
  "tags": [
    "react",
    "modal",
    "styled-components"
  ],
  "author": "Alexander Richey <alexander.richey@gmail.com>",
  "license": "Unlicense",
  "bugs": {
    "url": "https://github.com/AlexanderRichey/styled-react-modal/issues"
  },
  "homepage": "https://github.com/AlexanderRichey/styled-react-modal#readme",
  "peerDependencies": {
    "react": ">=18 <19",
    "react-dom": ">=18 <19",
    "styled-components": "^6.1.1"
  },
  "devDependencies": {
    "@babel/core": "^7.19.1",
    "@babel/plugin-transform-runtime": "^7.19.1",
    "@babel/preset-env": "^7.19.1",
    "@babel/preset-react": "^7.18.6",
    "@rollup/plugin-babel": "^5.3.1",
    "@rollup/plugin-node-resolve": "^14.1.0",
    "@rollup/plugin-replace": "^4.0.0",
    "@testing-library/dom": "^8.18.1",
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
    "babel-eslint": "^10.1.0",
    "babel-jest": "^29.0.3",
    "babel-plugin-styled-components": "^2.0.7",
    "babel-plugin-transform-object-rest-spread": "^6.26.0",
    "eslint": "^8.23.1",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-prettier": "^4.2.1",
    "eslint-plugin-react": "^7.31.8",
    "jest": "^29.0.3",
    "jest-environment-jsdom": "^29.0.3",
    "prettier": "^2.7.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-is": "^18.2.0",
    "react-test-renderer": "^18.2.0",
    "rollup": "^2.79.0",
    "rollup-plugin-commonjs": "^10.1.0",
    "rollup-plugin-terser": "^7.0.2",
    "styled-components": "^6.1.1"
  },
  "resolutions": {
    "eslint-utils": "^2.0.0",
    "mixin-deep": "^2.0.1",
    "set-value": "^3.0.2",
    "minimist": "^0.2.1",
    "acorn": "^6.4.1",
    "kind-of": "^6.0.3",
    "serialize-javascript": "^3.1.0",
    "ssri": "^8.0.1",
    "node-notifier": "^8.0.1",
    "terser": "5.14.2",
    "ajv": "6.12.6"
  },
  "jest": {
    "coverageDirectory": "./coverage/",
    "collectCoverage": true,
    "testEnvironment": "jsdom"
  },
  "dependencies": {
    "prop-types": "^15.8.1"
  },
  "packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447"
}


================================================
FILE: rollup.config.js
================================================
import resolve from "@rollup/plugin-node-resolve";
import replace from "@rollup/plugin-replace";
import { terser } from "rollup-plugin-terser";
import babel from "@rollup/plugin-babel";
import commonjs from "rollup-plugin-commonjs";

export default {
  input: "src/index.jsx",
  output: [
    {
      name: "styled-react-modal",
      file: "build/umd/index.js",
      format: "umd",
      exports: "named",
      globals: {
        react: "React",
        "react-dom": "ReactDOM",
        "styled-components": "styled"
      }
    },
    {
      name: "styled-react-modal",
      exports: "named",
      file: "build/mjs/index.mjs",
      format: "es"
    }
  ],
  plugins: [
    resolve({
      extensions: [".jsx", ".js"],
      moduleDirectories: ["node_modules"]
    }),
    replace({
      "process.env.NODE_ENV": JSON.stringify("production"),
      preventAssignment: true
    }),
    commonjs({
      include: /node_modules/
    }),
    babel({
      babelHelpers: "runtime",
      extensions: [".jsx", ".js"],
      exclude: /(node_modules|build)/
    }),
    terser()
  ],
  external: ["react", "react-dom", "styled-components"]
};


================================================
FILE: src/Modal.jsx
================================================
import React, { useState, useRef, useEffect } from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";
import PropTypes from "prop-types";
import { Consumer } from "./context";

function callIfDefined(fun, ...args) {
  fun && fun(...args);
}

function Modal({
  WrapperComponent,
  children,
  onBackgroundClick,
  onEscapeKeydown,
  allowScroll,
  beforeOpen,
  afterOpen,
  beforeClose,
  afterClose,
  backgroundProps = {},
  isOpen: isOpenProp,
  ...rest
}) {
  const node = useRef(null);
  const prevBodyOverflowStyle = useRef(null);
  const isTransitioning = useRef(false);

  const [isOpen, setIsOpen] = useState(false);

  // Handle opening and closing
  useEffect(() => {
    function handleIsOpenChange(value) {
      setIsOpen(value);
      value ? callIfDefined(afterOpen) : callIfDefined(afterClose);
    }

    function handleChange(callback) {
      if (callback) {
        const maybePromise = callback();
        if (typeof maybePromise?.then === "function") {
          isTransitioning.current = true;

          maybePromise.then(() => {
            handleIsOpenChange(isOpenProp);

            isTransitioning.current = false;
          });
        } else {
          handleIsOpenChange(isOpenProp);
        }
      } else {
        handleIsOpenChange(isOpenProp);
      }
    }

    if (isOpen !== isOpenProp && !isTransitioning.current) {
      if (isOpenProp) {
        handleChange(beforeOpen);
      } else {
        handleChange(beforeClose);
      }
    }
  }, [
    isTransitioning,
    isOpen,
    isOpenProp,
    beforeOpen,
    beforeClose,
    afterClose,
    afterOpen
  ]);

  // Handle Escape keydown
  useEffect(() => {
    function handleKeydown(e) {
      if (e.key === "Escape") {
        onEscapeKeydown && onEscapeKeydown(e);
      }
    }

    if (isOpen) {
      document.addEventListener("keydown", handleKeydown);
    }

    return () => {
      document.removeEventListener("keydown", handleKeydown);
    };
  }, [isOpen, onEscapeKeydown]);

  // Handle changing document.body styles based on isOpen state
  useEffect(() => {
    if (isOpen && !allowScroll) {
      prevBodyOverflowStyle.current = document.body.style.overflow;
      document.body.style.overflow = "hidden";
    }

    return () => {
      if (!allowScroll) {
        document.body.style.overflow = prevBodyOverflowStyle.current || "";
      }
    };
  }, [isOpen, allowScroll]);

  function handleBackgroundClick(e) {
    if (node.current === e.target) {
      onBackgroundClick && onBackgroundClick(e);
    }
  }

  let content;
  if (WrapperComponent) {
    content = <WrapperComponent {...rest}>{children}</WrapperComponent>;
  } else {
    content = children;
  }

  return (
    <Consumer>
      {({ modalNode, BackgroundComponent }) => {
        if (modalNode && BackgroundComponent && isOpen) {
          return ReactDOM.createPortal(
            <BackgroundComponent
              {...backgroundProps}
              onClick={handleBackgroundClick}
              ref={node}
            >
              {content}
            </BackgroundComponent>,
            modalNode
          );
        } else {
          return null;
        }
      }}
    </Consumer>
  );
}

Modal.styled = function (...args) {
  const wrap = args ? styled.div(...args) : styled.div();

  return function (props) {
    return <Modal WrapperComponent={wrap} {...props} />;
  };
};

Modal.propTypes = {
  WrapperComponent: PropTypes.oneOfType([PropTypes.element, PropTypes.object]),
  onBackgroundClick: PropTypes.func,
  onEscapeKeydown: PropTypes.func,
  allowScroll: PropTypes.bool,
  beforeOpen: PropTypes.func,
  afterOpen: PropTypes.func,
  beforeClose: PropTypes.func,
  afterClose: PropTypes.func,
  backgroundProps: PropTypes.object,
  isOpen: PropTypes.bool
};

export default Modal;


================================================
FILE: src/ModalProvider.jsx
================================================
import React, { useState, useEffect, useRef } from "react";
import PropTypes from "prop-types";
import { BaseModalBackground } from "./baseStyles";
import { Provider } from "./context";

function ModalProvider({
  backgroundComponent: propsBackgroundComponent,
  children
}) {
  const modalNode = useRef(null);
  const [stateModalNode, setStateModalNode] = useState(null);
  const [BackgroundComponent, setBackgroundComponent] = useState(
    BaseModalBackground
  );

  useEffect(() => {
    if (propsBackgroundComponent) {
      setBackgroundComponent(propsBackgroundComponent);
    }
  }, [setBackgroundComponent, propsBackgroundComponent]);

  useEffect(() => {
    setStateModalNode(modalNode.current);
  }, [setStateModalNode, modalNode]);

  return (
    <Provider
      value={{
        modalNode: stateModalNode,
        BackgroundComponent: BackgroundComponent
      }}
    >
      {children}
      <div ref={modalNode} />
    </Provider>
  );
}

ModalProvider.propTypes = {
  backgroundComponent: PropTypes.oneOfType([
    PropTypes.element,
    PropTypes.object
  ])
};

export default ModalProvider;


================================================
FILE: src/baseStyles.jsx
================================================
import styled from 'styled-components'

export const BaseModalBackground = styled.div`
  display: flex;
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: 30;
  background-color: rgba(0, 0, 0, 0.5);
  align-items: center;
  justify-content: center;
`


================================================
FILE: src/context.jsx
================================================
import React from 'react'

export const { Provider, Consumer } = React.createContext({})


================================================
FILE: src/index.jsx
================================================
import { BaseModalBackground } from "./baseStyles";
import ModalProvider from "./ModalProvider";
import Modal from "./Modal";

export { Modal as default, ModalProvider, BaseModalBackground };


================================================
FILE: tests/Modal.test.jsx
================================================
import "@testing-library/jest-dom/extend-expect";

import React, { useState } from "react";
import { act } from "react-dom/test-utils";
import styled from "styled-components";
import { render, fireEvent, waitFor } from "@testing-library/react";
import Modal, { ModalProvider } from "../src";

function StatefulModal(props) {
  const { isOpen: propsIsOpen, ...rest } = props;
  const [isOpen, setIsOpen] = useState(propsIsOpen);
  return (
    <div>
      <button data-testid="button" onClick={() => setIsOpen(!isOpen)}>
        Click me
      </button>
      <Modal isOpen={isOpen} {...rest}>
        <span data-testid="content">Hello world</span>
      </Modal>
    </div>
  );
}

function renderWithProvider(modalProps = {}, providerProps = {}) {
  const finalModalProps = {
    isOpen: false,
    onBackgroundClick: jest.fn(),
    onEscapeKeyDown: jest.fn(),
    ...modalProps
  };

  return render(
    <ModalProvider data-testid="provider" {...providerProps}>
      <StatefulModal {...finalModalProps} />
    </ModalProvider>
  );
}

describe("<Modal />", () => {
  it("renders nothing when not open", () => {
    const { queryByText } = renderWithProvider();
    expect(queryByText("Hello world")).toBeNull();
  });

  it("renders children when open", () => {
    const { getByText } = renderWithProvider({ isOpen: true });
    expect(getByText("Hello world")).toBeTruthy();
  });

  it("calls onBackgroundClick when the background is clicked", () => {
    const spy = jest.fn();
    const { getByTestId } = renderWithProvider({
      onBackgroundClick: spy,
      isOpen: true,
      backgroundProps: { "data-testid": "background" }
    });
    fireEvent.click(getByTestId("background"));
    expect(spy.mock.calls.length).toBe(1);
  });

  it("calls onEscapeKeydown when the escape key is pressed", () => {
    const spy = jest.fn();
    renderWithProvider({
      isOpen: true,
      onEscapeKeydown: spy
    });
    fireEvent.keyDown(document, { key: "Escape" });
    expect(spy.mock.calls.length).toBe(1);
  });

  it("calls beforeOpen() before it opens", () => {
    const spy = jest.fn();
    const sleeper = jest.fn();
    const { getByTestId } = renderWithProvider({
      beforeOpen: spy,
      beforeClose: sleeper
    });
    fireEvent.click(getByTestId("button"));
    expect(spy.mock.calls.length).toBe(1);
    expect(sleeper).not.toHaveBeenCalled();
  });

  it("calls beforeOpen() before it opens and waits to call afterOpen() if it returns a promise", async () => {
    const spy = jest.fn(
      () => new Promise((resolve) => setTimeout(act(resolve), 100))
    );
    const afterOpenSpy = jest.fn();
    const sleeper = jest.fn();
    const { getByTestId } = renderWithProvider({
      beforeOpen: spy,
      afterOpen: afterOpenSpy,
      beforeClose: sleeper
    });
    fireEvent.click(getByTestId("button"));
    expect(spy.mock.calls.length).toBe(1);
    await waitFor(() => expect(afterOpenSpy.mock.calls.length).toBe(1));
    expect(sleeper).not.toHaveBeenCalled();
  });

  it("calls afterOpen() after it opens", () => {
    const spy = jest.fn();
    const sleeper = jest.fn();
    const { getByTestId } = renderWithProvider({
      afterOpen: spy,
      afterClose: sleeper
    });
    fireEvent.click(getByTestId("button"));
    expect(spy.mock.calls.length).toBe(1);
    expect(sleeper).not.toHaveBeenCalled();
  });

  it("calls beforeClose() before it closes", () => {
    const spy = jest.fn();
    const sleeper = jest.fn();
    const { getByTestId } = renderWithProvider({
      isOpen: true,
      beforeClose: spy,
      beforeOpen: sleeper
    });
    fireEvent.click(getByTestId("button"));
    expect(spy.mock.calls.length).toBe(1);
    expect(sleeper.mock.calls.length).toBe(1);
  });

  it("calls beforeClose() before it closes and waits to call afterClose() if it returns a promise", async () => {
    const spy = jest.fn(
      () => new Promise((resolve) => setTimeout(act(resolve), 100))
    );
    const afterCloseSpy = jest.fn();
    const sleeper = jest.fn();
    const { getByTestId } = renderWithProvider({
      isOpen: true,
      beforeClose: spy,
      afterClose: afterCloseSpy,
      beforeOpen: sleeper
    });
    fireEvent.click(getByTestId("button"));
    expect(spy.mock.calls.length).toBe(1);
    await waitFor(() => expect(afterCloseSpy.mock.calls.length).toBe(1));
    expect(sleeper.mock.calls.length).toBe(1);
  });

  it("calls afterClose() after it closes", () => {
    const spy = jest.fn();
    const sleeper = jest.fn();
    const { getByTestId } = renderWithProvider({
      isOpen: true,
      afterClose: spy,
      afterOpen: sleeper
    });
    fireEvent.click(getByTestId("button"));
    expect(spy.mock.calls.length).toBe(1);
    expect(sleeper.mock.calls.length).toBe(1);
  });

  it("passes background props to background", () => {
    const Background = styled.div`
      background: ${(props) => props.color || "green"};
    `;

    const { getByTestId } = renderWithProvider(
      {
        isOpen: true,
        backgroundProps: { color: "blue", "data-testid": "background" }
      },
      {
        backgroundComponent: Background
      }
    );

    expect(getByTestId("background")).toHaveStyle(`background: blue`);
  });
});

describe("Modal.styled()", () => {
  it("returns to a <Modal /> instance", () => {
    const StyledModal = Modal.styled`
      background-color: green;
    `;

    const { getByTestId } = render(
      <ModalProvider>
        <StyledModal isOpen={true} data-testid="modal" />
      </ModalProvider>
    );

    expect(getByTestId("modal")).toHaveStyle(`background-color: green`);
  });
});
Download .txt
gitextract_2y96oc27/

├── .babelrc
├── .circleci/
│   └── config.yml
├── .codesandbox/
│   └── ci.json
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .hound.yml
├── .npmignore
├── .nvmrc
├── .prettierrc
├── LICENSE
├── README.md
├── build/
│   ├── mjs/
│   │   └── index.mjs
│   └── umd/
│       └── index.js
├── package.json
├── rollup.config.js
├── src/
│   ├── Modal.jsx
│   ├── ModalProvider.jsx
│   ├── baseStyles.jsx
│   ├── context.jsx
│   └── index.jsx
└── tests/
    └── Modal.test.jsx
Download .txt
SYMBOL INDEX (28 symbols across 5 files)

FILE: build/mjs/index.mjs
  function l (line 1) | function l(e,n){(null==n||n>e.length)&&(n=e.length);for(var r=0,t=new Ar...
  function i (line 1) | function i(e,n){return function(e){if(Array.isArray(e))return e}(e)||fun...
  function u (line 1) | function u(){}
  function f (line 1) | function f(){}
  function e (line 1) | function e(e,n,r,t,o,a){if("SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED...
  function n (line 1) | function n(){return e}
  function h (line 1) | function h(e){var a=e.backgroundComponent,l=e.children,u=r(null),f=i(t(n...
  function O (line 1) | function O(){return O=Object.assign?Object.assign.bind():function(e){for...
  function w (line 1) | function w(e,n){if(null==e)return{};var r,t,o=function(e,n){if(null==e)r...
  function k (line 1) | function k(e){for(var n=arguments.length,r=new Array(n>1?n-1:0),t=1;t<n;...
  function E (line 1) | function E(e){var c,l=e.WrapperComponent,u=e.children,f=e.onBackgroundCl...

FILE: build/umd/index.js
  function o (line 1) | function o(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}
  function f (line 1) | function f(e,n){(null==n||n>e.length)&&(n=e.length);for(var t=0,r=new Ar...
  function i (line 1) | function i(e,n){return function(e){if(Array.isArray(e))return e}(e)||fun...
  function p (line 1) | function p(){}
  function d (line 1) | function d(){}
  function e (line 1) | function e(e,n,t,r,o,a){if("SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED...
  function n (line 1) | function n(){return e}
  function g (line 1) | function g(e){var n=e.backgroundComponent,r=e.children,o=t.useRef(null),...
  function h (line 1) | function h(){return h=Object.assign?Object.assign.bind():function(e){for...
  function O (line 1) | function O(e,n){if(null==e)return{};var t,r,o=function(e,n){if(null==e)r...
  function C (line 1) | function C(e){for(var n=arguments.length,t=new Array(n>1?n-1:0),r=1;r<n;...
  function k (line 1) | function k(e){var n,r=e.WrapperComponent,o=e.children,a=e.onBackgroundCl...

FILE: src/Modal.jsx
  function callIfDefined (line 7) | function callIfDefined(fun, ...args) {
  function Modal (line 11) | function Modal({

FILE: src/ModalProvider.jsx
  function ModalProvider (line 6) | function ModalProvider({

FILE: tests/Modal.test.jsx
  function StatefulModal (line 9) | function StatefulModal(props) {
  function renderWithProvider (line 24) | function renderWithProvider(modalProps = {}, providerProps = {}) {
Condensed preview — 22 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (37K chars).
[
  {
    "path": ".babelrc",
    "chars": 203,
    "preview": "{\n  \"presets\": [\"@babel/env\", \"@babel/react\"],\n  \"plugins\": [\n    [\"@babel/plugin-transform-runtime\"],\n    [\"babel-plugi"
  },
  {
    "path": ".circleci/config.yml",
    "chars": 802,
    "preview": "version: 2.1\n\n# Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to"
  },
  {
    "path": ".codesandbox/ci.json",
    "chars": 33,
    "preview": "{\n  \"sandboxes\": [\"m9jlky57y\"]\n}\n"
  },
  {
    "path": ".eslintignore",
    "chars": 31,
    "preview": "build/\nnode_modules/\ncoverage/\n"
  },
  {
    "path": ".eslintrc",
    "chars": 92,
    "preview": "{\n  \"extends\": \"prettier\",\n  \"parser\": \"babel-eslint\",\n  \"plugins\": [\"prettier\", \"react\"]\n}\n"
  },
  {
    "path": ".gitignore",
    "chars": 104,
    "preview": "# dependencies\n/node_modules\n\n# misc\n.DS_Store\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n/coverage\n"
  },
  {
    "path": ".hound.yml",
    "chars": 78,
    "preview": "eslint:\n  enabled: true\n  config_file: .eslintrc\n  ignore_file: .eslintignore\n"
  },
  {
    "path": ".npmignore",
    "chars": 10,
    "preview": "coverage/\n"
  },
  {
    "path": ".nvmrc",
    "chars": 8,
    "preview": "v18.7.0\n"
  },
  {
    "path": ".prettierrc",
    "chars": 202,
    "preview": "{\n  \"printWidth\": 80,\n  \"tabWidth\": 2,\n  \"useTabs\": false,\n  \"semi\": true,\n  \"singleQuote\": false,\n  \"trailingComma\": \"n"
  },
  {
    "path": "LICENSE",
    "chars": 1247,
    "preview": "Copyright (c) 2022 Alexander Richey\n\nThis is free and unencumbered software released into the public domain.\n\nAnyone is "
  },
  {
    "path": "README.md",
    "chars": 6211,
    "preview": "# Styled React Modal\n\n[![style: styled-components](https://img.shields.io/badge/style-%F0%9F%92%85%20styled--components-"
  },
  {
    "path": "build/mjs/index.mjs",
    "chars": 5034,
    "preview": "import e from\"styled-components\";import n,{useRef as r,useState as t,useEffect as o}from\"react\";import a from\"react-dom\""
  },
  {
    "path": "build/umd/index.js",
    "chars": 5647,
    "preview": "!function(e,n){\"object\"==typeof exports&&\"undefined\"!=typeof module?n(exports,require(\"styled-components\"),require(\"reac"
  },
  {
    "path": "package.json",
    "chars": 2666,
    "preview": "{\n  \"name\": \"styled-react-modal\",\n  \"version\": \"3.1.2\",\n  \"description\": \"A React modal built with styled-components.\",\n"
  },
  {
    "path": "rollup.config.js",
    "chars": 1142,
    "preview": "import resolve from \"@rollup/plugin-node-resolve\";\nimport replace from \"@rollup/plugin-replace\";\nimport { terser } from "
  },
  {
    "path": "src/Modal.jsx",
    "chars": 3818,
    "preview": "import React, { useState, useRef, useEffect } from \"react\";\nimport ReactDOM from \"react-dom\";\nimport styled from \"styled"
  },
  {
    "path": "src/ModalProvider.jsx",
    "chars": 1113,
    "preview": "import React, { useState, useEffect, useRef } from \"react\";\nimport PropTypes from \"prop-types\";\nimport { BaseModalBackgr"
  },
  {
    "path": "src/baseStyles.jsx",
    "chars": 284,
    "preview": "import styled from 'styled-components'\n\nexport const BaseModalBackground = styled.div`\n  display: flex;\n  position: fixe"
  },
  {
    "path": "src/context.jsx",
    "chars": 89,
    "preview": "import React from 'react'\n\nexport const { Provider, Consumer } = React.createContext({})\n"
  },
  {
    "path": "src/index.jsx",
    "chars": 192,
    "preview": "import { BaseModalBackground } from \"./baseStyles\";\nimport ModalProvider from \"./ModalProvider\";\nimport Modal from \"./Mo"
  },
  {
    "path": "tests/Modal.test.jsx",
    "chars": 5614,
    "preview": "import \"@testing-library/jest-dom/extend-expect\";\n\nimport React, { useState } from \"react\";\nimport { act } from \"react-d"
  }
]

About this extraction

This page contains the full source code of the AlexanderRichey/styled-react-modal GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 22 files (33.8 KB), approximately 10.1k tokens, and a symbol index with 28 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!