From d53aab19ef7b9896f85a2c17c003c27e1a829a2e Mon Sep 17 00:00:00 2001 From: Mikhail Pontus Date: Thu, 29 Nov 2018 19:11:52 +0300 Subject: [PATCH] Initial implementation --- example/package.json | 7 ++-- example/src/index.js | 8 +++-- example/yarn.lock | 26 ++++++-------- package.json | 11 +++--- src/ModalContext.ts | 30 ++++++++++++++++ src/ModalProvider.tsx | 37 ++++++++++++++++++++ src/ModalRoot.tsx | 18 ++++++++++ src/__tests__/useModal.tsx | 38 ++++++++++++++++++++ src/index.tsx | 27 +++------------ src/styles.css | 8 ----- src/test.ts | 7 ---- src/typings.d.ts | 17 --------- src/useModal.ts | 21 +++++++++++ tsconfig.json | 3 +- yarn.lock | 71 +++++++++++++++++++++++++++++++------- 15 files changed, 234 insertions(+), 95 deletions(-) create mode 100644 src/ModalContext.ts create mode 100644 src/ModalProvider.tsx create mode 100644 src/ModalRoot.tsx create mode 100644 src/__tests__/useModal.tsx delete mode 100644 src/styles.css delete mode 100644 src/test.ts delete mode 100644 src/typings.d.ts create mode 100644 src/useModal.ts diff --git a/example/package.json b/example/package.json index 20af259..29d897a 100644 --- a/example/package.json +++ b/example/package.json @@ -6,10 +6,9 @@ "private": true, "dependencies": { "prop-types": "^15.6.2", - "react": "^16.4.1", - "react-dom": "^16.4.1", - "react-scripts": "^1.1.4", - "react-modal-hook": "link:.." + "react": "^16.7.0-alpha.2", + "react-dom": "^16.7.0-alpha.2", + "react-scripts": "^1.1.4" }, "scripts": { "start": "react-scripts start", diff --git a/example/src/index.js b/example/src/index.js index cc5dbe4..8556643 100644 --- a/example/src/index.js +++ b/example/src/index.js @@ -5,8 +5,10 @@ import "./index.css"; import App from "./App"; ReactDOM.render( - - - , +
+ + + +
, document.getElementById("root") ); diff --git a/example/yarn.lock b/example/yarn.lock index 1a1c341..0adb884 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -5467,23 +5467,19 @@ react-dev-utils@^5.0.2: strip-ansi "3.0.1" text-table "0.2.0" -react-dom@^16.4.1: - version "16.6.3" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.6.3.tgz#8fa7ba6883c85211b8da2d0efeffc9d3825cccc0" +react-dom@^16.7.0-alpha.2: + version "16.7.0-alpha.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.7.0-alpha.2.tgz#16632880ed43676315991d8b412cce6975a30282" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.11.2" + scheduler "^0.12.0-alpha.2" react-error-overlay@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.1.tgz#417addb0814a90f3a7082eacba7cee588d00da89" -"react-modal-hook@link:..": - version "0.0.0" - uid "" - react-scripts@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-1.1.5.tgz#3041610ab0826736b52197711a4c4e3756e97768" @@ -5529,14 +5525,14 @@ react-scripts@^1.1.4: optionalDependencies: fsevents "^1.1.3" -react@^16.4.1: - version "16.6.3" - resolved "https://registry.yarnpkg.com/react/-/react-16.6.3.tgz#25d77c91911d6bbdd23db41e70fb094cc1e0871c" +react@^16.7.0-alpha.2: + version "16.7.0-alpha.2" + resolved "https://registry.yarnpkg.com/react/-/react-16.7.0-alpha.2.tgz#924f2ae843a46ea82d104a8def7a599fbf2c78ce" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.11.2" + scheduler "^0.12.0-alpha.2" read-pkg-up@^1.0.1: version "1.0.1" @@ -5889,9 +5885,9 @@ sax@^1.2.1, sax@^1.2.4, sax@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" -scheduler@^0.11.2: - version "0.11.2" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.11.2.tgz#a8db5399d06eba5abac51b705b7151d2319d33d3" +scheduler@^0.12.0-alpha.2: + version "0.12.0-alpha.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.12.0-alpha.2.tgz#2a8bc8dc6ecdb75fa6480ceeedc1f187c9539970" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" diff --git a/package.json b/package.json index 1cb49ad..6068a23 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,8 @@ "dependencies": {}, "peerDependencies": { "prop-types": "^15.5.4", - "react": "^15.0.0 || ^16.0.0", - "react-dom": "^15.0.0 || ^16.0.0" + "react": "^16.7.0-alpha.0", + "react-dom": "16.7.0-alpha.0" }, "devDependencies": { "@svgr/rollup": "^2.4.1", @@ -36,9 +36,10 @@ "babel-runtime": "^6.26.0", "cross-env": "^5.1.4", "gh-pages": "^1.2.0", - "react": "^16.4.1", - "react-dom": "^16.4.1", + "react": "^16.7.0-alpha.2", + "react-dom": "16.7.0-alpha.2", "react-scripts-ts": "^2.16.0", + "react-testing-library": "^5.3.1", "rollup": "^0.62.0", "rollup-plugin-babel": "^3.0.7", "rollup-plugin-commonjs": "^9.1.3", @@ -47,7 +48,7 @@ "rollup-plugin-postcss": "^1.6.2", "rollup-plugin-typescript2": "^0.17.0", "rollup-plugin-url": "^1.4.0", - "typescript": "^2.8.3" + "typescript": "^3.1.6" }, "files": [ "dist" diff --git a/src/ModalContext.ts b/src/ModalContext.ts new file mode 100644 index 0000000..b49c6b9 --- /dev/null +++ b/src/ModalContext.ts @@ -0,0 +1,30 @@ +import React from "react"; + +/** + * Placeholder for context callbacks + */ +const noop = () => undefined; + +/** + * Modals, represented as stateless functions, are react + * components, but class components will work also. + */ +export type ModalType = React.ComponentType; + +/** + * Shape of the modal context + */ +export interface ModalContextType { + modal: ModalType | undefined; + showModal(component: ModalType): void; + hideModal(): void; +} + +/** + * Modal Context Object + */ +export const ModalContext = React.createContext({ + modal: undefined, + showModal: noop, + hideModal: noop +}); diff --git a/src/ModalProvider.tsx b/src/ModalProvider.tsx new file mode 100644 index 0000000..70ab099 --- /dev/null +++ b/src/ModalProvider.tsx @@ -0,0 +1,37 @@ +import React, { useState } from "react"; +import { ModalType, ModalContext } from "./ModalContext"; +import { ModalRoot } from "./ModalRoot"; + +/** + * Modal Provider Props + */ +export interface ModalProviderProps { + /** + * Children which will receive Modal Context + */ + children: React.ReactNode; +} + +/** + * Modal Provider + * + * Provides Modal Context to children. + */ +export const ModalProvider = ({ children }: ModalProviderProps) => { + const [modal, setModal] = useState(undefined); + + return ( + setModal(() => modal), + hideModal: () => setModal(undefined) + }} + > + + {children} + + + + ); +}; diff --git a/src/ModalRoot.tsx b/src/ModalRoot.tsx new file mode 100644 index 0000000..9c3867b --- /dev/null +++ b/src/ModalRoot.tsx @@ -0,0 +1,18 @@ +import React, { useContext } from "react"; +import ReactDOM from "react-dom"; +import { ModalContext } from "./ModalContext"; + +/** + * Modal Root + * + * Renders modals using a portal. + */ +export const ModalRoot = () => { + const { modal: Component } = useContext(ModalContext); + + if (Component === undefined) { + return null; + } + + return ReactDOM.createPortal(, document.body); +}; diff --git a/src/__tests__/useModal.tsx b/src/__tests__/useModal.tsx new file mode 100644 index 0000000..a7c251f --- /dev/null +++ b/src/__tests__/useModal.tsx @@ -0,0 +1,38 @@ +import "react-testing-library/cleanup-after-each"; +import * as React from "react"; +import { render, fireEvent } from "react-testing-library"; +import { ModalProvider } from "../ModalProvider"; +import { useModal } from "../useModal"; + +// Helper to render components in modal context +const renderWithProvider = (content: React.ReactNode) => + render({content}); + +// Test component which calls useModal +const Component = () => { + const [showModal, hideModal] = useModal(() =>
Modal content
); + + return ( + + + + + ); +}; + +test("showModal works", () => { + const { getByText } = renderWithProvider(); + + fireEvent.click(getByText("Show modal")); + + expect(getByText("Modal content")).toBeTruthy(); +}); + +test("hideModal works", () => { + const { getByText, queryByText } = renderWithProvider(); + + fireEvent.click(getByText("Show modal")); + fireEvent.click(getByText("Hide modal")); + + expect(queryByText("Modal content")).not.toBeTruthy(); +}); diff --git a/src/index.tsx b/src/index.tsx index f114363..ad20fb3 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,23 +1,4 @@ -/** - * @class ExampleComponent - */ - -import * as React from 'react' - -import styles from './styles.css' - -export type Props = { text: string } - -export default class ExampleComponent extends React.Component { - render() { - const { - text - } = this.props - - return ( -
- Example Component: {text} -
- ) - } -} +export * from "./ModalContext"; +export * from "./ModalProvider"; +export * from "./ModalRoot"; +export * from "./useModal"; diff --git a/src/styles.css b/src/styles.css deleted file mode 100644 index 44c1f06..0000000 --- a/src/styles.css +++ /dev/null @@ -1,8 +0,0 @@ -/* add css styles here (optional) */ - -.test { - display: inline-block; - margin: 2em auto; - border: 2px solid #000; - font-size: 2em; -} diff --git a/src/test.ts b/src/test.ts deleted file mode 100644 index d7cac64..0000000 --- a/src/test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import ExampleComponent from './' - -describe('ExampleComponent', () => { - it('is truthy', () => { - expect(ExampleComponent).toBeTruthy() - }) -}) diff --git a/src/typings.d.ts b/src/typings.d.ts deleted file mode 100644 index cd16102..0000000 --- a/src/typings.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Default CSS definition for typescript, - * will be overridden with file-specific definitions by rollup - */ -declare module '*.css' { - const content: { [className: string]: string }; - export default content; -} - -interface SvgrComponent extends React.StatelessComponent> {} - -declare module '*.svg' { - const svgUrl: string; - const svgComponent: SvgrComponent; - export default svgUrl; - export { svgComponent as ReactComponent } -} diff --git a/src/useModal.ts b/src/useModal.ts new file mode 100644 index 0000000..96c8a7e --- /dev/null +++ b/src/useModal.ts @@ -0,0 +1,21 @@ +import { useContext } from "react"; +import { ModalType, ModalContext } from "./ModalContext"; + +/** + * Callback for showing the modal + */ +type ShowModal = () => void; + +/** + * Callback for hiding the modal + */ +type HideModal = () => void; + +/** + * React hook for showing modal windows + */ +export const useModal = (modal: ModalType): [ShowModal, HideModal] => { + const { showModal, hideModal } = useContext(ModalContext); + + return [() => showModal(modal), () => hideModal()]; +}; diff --git a/tsconfig.json b/tsconfig.json index 83923ad..a82f99b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,8 @@ "strictNullChecks": true, "suppressImplicitAnyIndexErrors": true, "noUnusedLocals": true, - "noUnusedParameters": true + "noUnusedParameters": true, + "allowSyntheticDefaultImports": true }, "include": ["src"], "exclude": ["node_modules", "build", "dist", "example", "rollup.config.js"] diff --git a/yarn.lock b/yarn.lock index 40d9e93..0725b77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -546,6 +546,12 @@ "@babel/plugin-transform-react-jsx-self" "^7.0.0" "@babel/plugin-transform-react-jsx-source" "^7.0.0" +"@babel/runtime@^7.1.5": + version "7.1.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.1.5.tgz#4170907641cf1f61508f563ece3725150cc6fe39" + dependencies: + regenerator-runtime "^0.12.0" + "@babel/template@^7.1.0", "@babel/template@^7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644" @@ -576,6 +582,10 @@ lodash "^4.17.10" to-fast-properties "^2.0.0" +"@sheerun/mutationobserver-shim@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b" + "@svgr/core@^2.4.1": version "2.4.1" resolved "https://registry.yarnpkg.com/@svgr/core/-/core-2.4.1.tgz#03a407c28c4a1d84305ae95021e8eabfda8fa731" @@ -2810,6 +2820,15 @@ dom-serializer@0: domelementtype "~1.1.1" entities "~1.1.1" +dom-testing-library@^3.12.0: + version "3.12.5" + resolved "https://registry.yarnpkg.com/dom-testing-library/-/dom-testing-library-3.12.5.tgz#81f827e03519a9842d1066a76e7c38063d859f78" + dependencies: + "@babel/runtime" "^7.1.5" + "@sheerun/mutationobserver-shim" "^0.3.2" + pretty-format "^23.6.0" + wait-for-expect "^1.1.0" + dom-urls@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/dom-urls/-/dom-urls-1.1.0.tgz#001ddf81628cd1e706125c7176f53ccec55d918e" @@ -6413,6 +6432,13 @@ pretty-format@^22.4.0, pretty-format@^22.4.3: ansi-regex "^3.0.0" ansi-styles "^3.2.0" +pretty-format@^23.6.0: + version "23.6.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.6.0.tgz#5eaac8eeb6b33b987b7fe6097ea6a8a146ab5760" + dependencies: + ansi-regex "^3.0.0" + ansi-styles "^3.2.0" + private@^0.1.6, private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -6602,14 +6628,14 @@ react-dev-utils@^5.0.1: strip-ansi "3.0.1" text-table "0.2.0" -react-dom@^16.4.1: - version "16.6.3" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.6.3.tgz#8fa7ba6883c85211b8da2d0efeffc9d3825cccc0" +react-dom@16.7.0-alpha.0: + version "16.7.0-alpha.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.7.0-alpha.0.tgz#8379158d4c76d63c989f325f45dfa5762582584f" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.11.2" + scheduler "^0.11.0-alpha.0" react-error-overlay@^4.0.1: version "4.0.1" @@ -6659,14 +6685,20 @@ react-scripts-ts@^2.16.0: optionalDependencies: fsevents "^1.1.3" -react@^16.4.1: - version "16.6.3" - resolved "https://registry.yarnpkg.com/react/-/react-16.6.3.tgz#25d77c91911d6bbdd23db41e70fb094cc1e0871c" +react-testing-library@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/react-testing-library/-/react-testing-library-5.3.1.tgz#d70ab711c8cef604fd70c9ec422d59e7e51ae112" + dependencies: + dom-testing-library "^3.12.0" + +react@^16.7.0-alpha.0: + version "16.7.0-alpha.2" + resolved "https://registry.yarnpkg.com/react/-/react-16.7.0-alpha.2.tgz#924f2ae843a46ea82d104a8def7a599fbf2c78ce" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.11.2" + scheduler "^0.12.0-alpha.2" read-pkg-up@^1.0.1: version "1.0.1" @@ -6774,6 +6806,10 @@ regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" +regenerator-runtime@^0.12.0: + version "0.12.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" + regenerator-transform@^0.10.0: version "0.10.1" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" @@ -7168,13 +7204,20 @@ saxes@^3.1.3: dependencies: xmlchars "^1.3.1" -scheduler@^0.11.2: +scheduler@^0.11.0-alpha.0: version "0.11.2" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.11.2.tgz#a8db5399d06eba5abac51b705b7151d2319d33d3" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" +scheduler@^0.12.0-alpha.2: + version "0.12.0-alpha.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.12.0-alpha.2.tgz#2a8bc8dc6ecdb75fa6480ceeedc1f187c9539970" + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + schema-utils@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf" @@ -8030,9 +8073,9 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@^2.8.3: - version "2.9.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" +typescript@^3.1.6: + version "3.1.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68" uglify-es@^3.3.4: version "3.3.9" @@ -8310,6 +8353,10 @@ w3c-xmlserializer@^1.0.0: webidl-conversions "^4.0.2" xml-name-validator "^3.0.0" +wait-for-expect@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-1.1.0.tgz#6607375c3f79d32add35cd2c87ce13f351a3d453" + walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"