Skip to content

Commit

Permalink
Initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
mpontus committed Nov 29, 2018
1 parent f34f8f2 commit d53aab1
Show file tree
Hide file tree
Showing 15 changed files with 234 additions and 95 deletions.
7 changes: 3 additions & 4 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 5 additions & 3 deletions example/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import "./index.css";
import App from "./App";

ReactDOM.render(
<ModalProvider>
<App />
</ModalProvider>,
<div>
<ModalProvider>
<App />
</ModalProvider>
</div>,
document.getElementById("root")
);
26 changes: 11 additions & 15 deletions example/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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"
Expand Down
30 changes: 30 additions & 0 deletions src/ModalContext.ts
Original file line number Diff line number Diff line change
@@ -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<any>;

/**
* 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<ModalContextType>({
modal: undefined,
showModal: noop,
hideModal: noop
});
37 changes: 37 additions & 0 deletions src/ModalProvider.tsx
Original file line number Diff line number Diff line change
@@ -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<ModalType | undefined>(undefined);

return (
<ModalContext.Provider
value={{
modal,
showModal: modal => setModal(() => modal),
hideModal: () => setModal(undefined)
}}
>
<React.Fragment>
{children}
<ModalRoot />
</React.Fragment>
</ModalContext.Provider>
);
};
18 changes: 18 additions & 0 deletions src/ModalRoot.tsx
Original file line number Diff line number Diff line change
@@ -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(<Component />, document.body);
};
38 changes: 38 additions & 0 deletions src/__tests__/useModal.tsx
Original file line number Diff line number Diff line change
@@ -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(<ModalProvider>{content}</ModalProvider>);

// Test component which calls useModal
const Component = () => {
const [showModal, hideModal] = useModal(() => <div>Modal content</div>);

return (
<React.Fragment>
<button onClick={showModal}>Show modal</button>
<button onClick={hideModal}>Hide modal</button>
</React.Fragment>
);
};

test("showModal works", () => {
const { getByText } = renderWithProvider(<Component />);

fireEvent.click(getByText("Show modal"));

expect(getByText("Modal content")).toBeTruthy();
});

test("hideModal works", () => {
const { getByText, queryByText } = renderWithProvider(<Component />);

fireEvent.click(getByText("Show modal"));
fireEvent.click(getByText("Hide modal"));

expect(queryByText("Modal content")).not.toBeTruthy();
});
27 changes: 4 additions & 23 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -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<Props> {
render() {
const {
text
} = this.props

return (
<div className={styles.test}>
Example Component: {text}
</div>
)
}
}
export * from "./ModalContext";
export * from "./ModalProvider";
export * from "./ModalRoot";
export * from "./useModal";
8 changes: 0 additions & 8 deletions src/styles.css

This file was deleted.

7 changes: 0 additions & 7 deletions src/test.ts

This file was deleted.

17 changes: 0 additions & 17 deletions src/typings.d.ts

This file was deleted.

21 changes: 21 additions & 0 deletions src/useModal.ts
Original file line number Diff line number Diff line change
@@ -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()];
};
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
Loading

0 comments on commit d53aab1

Please sign in to comment.