Skip to content

Commit

Permalink
feat(example app): add modals page example 204 (#250)
Browse files Browse the repository at this point in the history
* WIP Set up basic layout, router

* Switch to react-router, add placeholder content

* feat(example app): add modals example page

* feat(example app): add redux for modals

* hook up modals page

* feat(example app): add hook modal

* fix(modals page): fixes from PR review

* fix(modals page): pr update

* fix(modals page): remove comment

* fix(modals page): add import back in

Co-authored-by: Suzanne Rozier <suz@truss.works>
  • Loading branch information
2 people authored and haworku committed Jun 29, 2020
1 parent e8e5d67 commit 482a0bb
Show file tree
Hide file tree
Showing 11 changed files with 311 additions and 11 deletions.
3 changes: 3 additions & 0 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"@types/react-redux": "^7.1.9",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-redux": "^7.2.0",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"redux": "^4.0.5",
"typescript": "~3.7.2"
},
"scripts": {
Expand Down
10 changes: 5 additions & 5 deletions example/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React from 'react';
import { render } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
// TODO Add additional app testing
test('renders the app without error', () => {
const { getByText } = render(<App />)
expect(getByText('Modals')).toBeInTheDocument()
})
11 changes: 10 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from 'react-router-dom'

import '@trussworks/react-uswds/lib/uswds.css'
import '@trussworks/react-uswds/lib/index.css'
import {
GovBanner,
Header,
Expand All @@ -19,18 +20,20 @@ import {

import HomePage from './pages/Home'
import ExamplePage from './pages/Example'
import ModalsPage from './pages/Modals'

import './App.css'

/* Routes */
const routes = {
HOME_PAGE: '/',
EXAMPLES_PAGE: '/examples',
MODALS_PAGE: '/modals',
}

const App = () => {
const [mobileNavOpen, setMobileNavOpen] = useState(false)
const { HOME_PAGE, EXAMPLES_PAGE } = routes
const { HOME_PAGE, EXAMPLES_PAGE, MODALS_PAGE } = routes

const toggleMobileNav = (): void => {
setMobileNavOpen((prevOpen) => !prevOpen)
Expand All @@ -43,6 +46,9 @@ const App = () => {
<NavLink to={EXAMPLES_PAGE} activeClassName="usa-current">
Examples
</NavLink>,
<NavLink to={MODALS_PAGE} activeClassName="usa-current">
Modals
</NavLink>,
]

return (
Expand Down Expand Up @@ -76,6 +82,9 @@ const App = () => {
<Route path={EXAMPLES_PAGE}>
<ExamplePage />
</Route>
<Route path={MODALS_PAGE}>
<ModalsPage />
</Route>
<Route path={HOME_PAGE}>
<HomePage />
</Route>
Expand Down
14 changes: 11 additions & 3 deletions example/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@ import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore } from 'redux';
import { Provider } from 'react-redux';

import { rootReducer } from './redux/reducers'

const store = createStore(rootReducer)

ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>,
document.getElementById('root')
);

Expand Down
98 changes: 98 additions & 0 deletions example/src/pages/Modals.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from 'react'
import {
Modal,
Button,
connectModal,
ConnectedModalProps,
useModal
} from '@trussworks/react-uswds'
import { connect, ConnectedProps } from 'react-redux'

import { openModalAction, closeModalAction } from './../redux/actions'
import { AppState, ModalState } from './../redux/types'


interface TestModalProps extends ConnectedModalProps {
modalKey?: string
children: React.ReactNode
}

const TestModal = ({ onClose, children }: TestModalProps): React.ReactElement => (
<Modal
title={<h2>Test Modal</h2>}
actions={
<>
<Button type="button" outline onClick={onClose}>
Cancel
</Button>
<Button type="button" onClick={onClose}>
Close
</Button>
</>
}>
{ children }
</Modal>
)

/** Example of Modal Component Using Redux */
const ConnectedTestModal = connectModal(TestModal)

const mapStateToProps = (state: AppState): ModalState => ({
openModalKey: state.modalState.openModalKey,
})

const mapDispatchToProps = {
openModalAction: openModalAction,
closeModalAction: closeModalAction,
}

const connector = connect(mapStateToProps, mapDispatchToProps)
/** End of Example of Modal Component Using Redux */

const ExamplePage = ({
openModalKey,
openModalAction,
closeModalAction,
}: ConnectedProps<typeof connector>): React.ReactElement => {

/** Example of Modal Component Using Hooks */
const { isOpen, openModal, closeModal } = useModal()
/** End of Example of Modal Component Using Hooks */

return (
<section>
<h1>Modals</h1>

<p className="usa-intro">
This page provides examples of how modals may be used within an
application.
</p>

<div style={{ margin: '8px'}}>
<Button type="button" onClick={() => openModalAction('testModal')}>
Click me!
</Button>
{/* Example of modal using redux for state management */}
<ConnectedTestModal
modalKey="testModal"
isOpen={'testModal' === openModalKey}
onClose={() => closeModalAction('testModal')}>
<p>This is a modal that uses redux!</p>
</ConnectedTestModal>
</div>
<div style={{ margin: '8px'}}>
<Button type="button" onClick={openModal}>
Click me!
</Button>
{/* Example of modal using hooks for state management */}
<ConnectedTestModal isOpen={isOpen} onClose={closeModal} >
<p>This is a modal that uses hooks!</p>
</ConnectedTestModal>
</div>
</section>
)
}

const ConnectedExamplePage = connector(ExamplePage)

export default ConnectedExamplePage
30 changes: 30 additions & 0 deletions example/src/redux/actions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
CLOSE_MODAL,
OPEN_MODAL,
} from './types'
import {
closeModalAction,
openModalAction,
} from './actions'

describe('Modal actions', () => {
describe('closeModal Action', () => {
it('returns the CLOSE_MODAL action type', () => {
const expectedAction = {
type: CLOSE_MODAL,
payload: 'testkey'
}
expect(closeModalAction('testkey')).toEqual(expectedAction)
})
})

describe('openModal Action', () => {
it('returns the OPEN_MODAL action type', () => {
const expectedAction = {
type: OPEN_MODAL,
payload: 'testkey'
}
expect(openModalAction('testkey')).toEqual(expectedAction)
})
})
})
16 changes: 16 additions & 0 deletions example/src/redux/actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

import { OPEN_MODAL, CLOSE_MODAL } from "./types";

export function openModalAction( modalKey: string) {
return {
type: OPEN_MODAL,
payload: modalKey
};
}

export function closeModalAction(modalKey: string) {
return {
type: CLOSE_MODAL,
payload: modalKey
};
}
34 changes: 34 additions & 0 deletions example/src/redux/reducer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { initialState, rootReducer } from './reducers'
import { OpenModalAction, CloseModalAction, AppState } from './types'

describe('rootReducer', () => {
it('should set the openModalKey state', () => {
const openModalAction: OpenModalAction = {
type: 'OPEN_MODAL',
payload: 'testModal',
}

const expectedState : AppState = {
modalState : {
openModalKey: 'testModal'
}
}

expect(rootReducer(initialState, openModalAction)).toEqual(expectedState)
})

it('should set the openModalKey state', () => {
const closeModalAction: CloseModalAction = {
type: 'CLOSE_MODAL',
payload: 'testModal',
}

const expectedState : AppState = {
modalState : {
openModalKey: ''
}
}

expect(rootReducer(initialState, closeModalAction)).toEqual(expectedState)
})
})
36 changes: 36 additions & 0 deletions example/src/redux/reducers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
OPEN_MODAL,
CLOSE_MODAL,
ModalActionTypes,
AppState
} from "./types";

export const initialState: AppState = {
modalState : {
openModalKey: ''
}
};

export function rootReducer (
state = initialState,
action: ModalActionTypes
): AppState {
switch (action.type) {
case OPEN_MODAL:
return {
...state,
modalState: {
openModalKey: action.payload
}
}
case CLOSE_MODAL:
return {
...state,
modalState: {
openModalKey: ''
}
}
default:
return state
}
}
24 changes: 24 additions & 0 deletions example/src/redux/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/** Modal Types */
export interface ModalState {
// string is the key of the open modal. This only allows a single modal to be open in the app at any time.
openModalKey: string
}

export const OPEN_MODAL = "OPEN_MODAL";
export const CLOSE_MODAL = "CLOSE_MODAL";

export interface OpenModalAction {
type: typeof OPEN_MODAL;
payload: string;
}

export interface CloseModalAction {
type: typeof CLOSE_MODAL;
payload: string;
}

export type ModalActionTypes = OpenModalAction | CloseModalAction;

export interface AppState {
modalState: ModalState
}
Loading

0 comments on commit 482a0bb

Please sign in to comment.