Skip to content

Commit

Permalink
feat: modal changes (#35)
Browse files Browse the repository at this point in the history
* feat: adhere more closely to the react lifecycle for modals

* feat: reduce boilerplate with a modal container

* feat: README

* chore: renames
  • Loading branch information
nicosantangelo authored and cazala committed Feb 19, 2019
1 parent dc7adae commit e3441f7
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 27 deletions.
52 changes: 43 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Common modules for our dApps
- [Navbar](https://github.com/decentraland/decentraland-dapps#navbar)
- [Footer](https://github.com/decentraland/decentraland-dapps#footer)
- [SignInPage](https://github.com/decentraland/decentraland-dapps#signinpage)
- [Modal](https://github.com/decentraland/decentraland-dapps#modal)
- [EtherscanLink](https://github.com/decentraland/decentraland-dapps#etherscanlink)
- [Components](https://github.com/decentraland/decentraland-dapps#components)
- [Intercom](https://github.com/decentraland/decentraland-dapps#intercom)
Expand Down Expand Up @@ -1059,27 +1060,25 @@ export { default as HelpModal } from './HelpModal'
Each modal will receive the properties defined on the `ModalComponent` type, found on `modules/modal/types`, so for example:

```tsx
import { Modal } from 'decentraland-ui'
import { ModalProps } from 'decentraland-dapps/dist/modules/modal/types'

type HelpModalProps = ModalProps & {
// Some custom props, maybe from a container
}

export default class HelpModal extends React.Component<HelpModalProps> {
onClose = () => {
const { modal, onClose } = this.props
closeModal(modal.name)
}

render() {
const { modal } = this.props
// Do something with modal.metadata
// The Modal component here can be whatever you like, just make sure to call closeModal(name) when you want to close it, to update the state
return <Modal open={modal.open} onClose={onClose} />
const { name, metadata, onClose } = this.props
// The Modal component here can be whatever you like, just make sure to call onClose when you want to close it, to update the state
// For more examples check the advanced usage
return <Modal open={true} className={name} onClose={onClose} />
}
}
```

If want to use [decentraland-ui's Modal](https://github.com/decentraland/ui) but you don't want to repeat the `open`, `className` and `onClose` props, you can use this module's [Modal](https://github.com/decentraland/decentraland-dapps#modal)

**Reducer**:

Add the `modalReducer` as `modal` to your `rootReducer`:
Expand Down Expand Up @@ -1434,6 +1433,41 @@ export default class MyApp extends React.Component {
}
```

## Modal

The `<Modal>` it's a shorthand for some common features used by modals provided to [ModalProvider](https://github.com/decentraland/decentraland-dapps#modal).

### Usage

```tsx
import * as React from 'react'
import Modal from 'decentraland-dapps/dist/containers/Modal'

export default class MyComponent extends React.PureComponent {
render() {
return (
const { name } = this.props

<Modal name={name} {/* Other Modal props from decentraland ui */>
<Modal.Header>
</Modal.Header>
<Modal.Description>
</Modal.Description>
</Modal>
)
}
}
```

Behind the scenes Modal is setting the following properties:

```js
open={true}
className={name}
size="small"
onClose={/*close the modal by name*/}
```

## EtherscanLink

The `<EtherscanLink>` can be used to link a transaction hash to Etherscan.io, and it connects to the redux store to know on which network the user is on.
Expand Down
1 change: 0 additions & 1 deletion src/components/Intercom/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
import Intercom from './Intercom'

export default Intercom
3 changes: 2 additions & 1 deletion src/containers/EtherscanLink/EtherscanLink.container.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { connect } from 'react-redux'
import { getNetwork } from '../../modules/wallet/selectors'

import EtherscanLink from './EtherscanLink'
import { MapStateProps, MapDispatchProps } from './EtherscanLink.types'
import { getNetwork } from '../../modules/wallet/selectors'

const mapState = (state: any): MapStateProps => {
return {
Expand Down
31 changes: 31 additions & 0 deletions src/containers/Modal/Modal.container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { connect } from 'react-redux'
import { RouterAction } from 'react-router-redux'

import Modal from './Modal'
import { ModalProps, MapStateProps, MapDispatchProps } from './Modal.types'
import { RootDispatch } from '../../types'
import { closeModal } from '../../modules/modal/actions'

const mapState = (_: any): MapStateProps => ({})

const mapDispatch = (
dispatch: RootDispatch<RouterAction>
): MapDispatchProps => ({
onCloseModal: (name: string) => dispatch(closeModal(name))
})

const mergeProps = (
stateProps: MapStateProps,
dispatchProps: MapDispatchProps,
ownProps: ModalProps
): ModalProps => ({
...stateProps,
...dispatchProps,
...ownProps
})

export default connect(
mapState,
mapDispatch,
mergeProps
)(Modal) as any
25 changes: 25 additions & 0 deletions src/containers/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from 'react'
import { Modal as ModalComponent } from 'decentraland-ui'

import { ModalProps } from './Modal.types'

export default class Modal extends React.PureComponent<ModalProps> {
handleClose = () => {
const { name, onCloseModal } = this.props
onCloseModal(name)
}

render() {
const { name, ...modalProps } = this.props

return (
<ModalComponent
open={true}
className={name}
size="small"
onClose={this.handleClose}
{...modalProps}
/>
)
}
}
25 changes: 25 additions & 0 deletions src/containers/Modal/Modal.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from 'react'
import {
ModalProps as ModalComponentProps,
ModalActions,
ModalContent,
ModalDescription,
ModalHeader
} from 'decentraland-ui'

import { closeModal } from '../../modules/modal/actions'

export type ModalProps = ModalComponentProps & {
name: string
onCloseModal: typeof closeModal
}

export interface ModalComponent extends React.PureComponent<ModalProps> {
Actions: typeof ModalActions
Content: typeof ModalContent
Description: typeof ModalDescription
Header: typeof ModalHeader
}

export type MapStateProps = {}
export type MapDispatchProps = Pick<ModalProps, 'onCloseModal'>
9 changes: 9 additions & 0 deletions src/containers/Modal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Modal as ModalComponent } from 'decentraland-ui'
import Modal from './Modal.container'

Modal.Actions = ModalComponent.Actions
Modal.Content = ModalComponent.Content
Modal.Description = ModalComponent.Description
Modal.Header = ModalComponent.Header

export default Modal
4 changes: 0 additions & 4 deletions src/modules/modal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,3 @@ export type Modal = {
name: string
metadata: any
}

export type ModalProps = {
modal: Modal
}
5 changes: 4 additions & 1 deletion src/providers/ModalProvider/ModalProvider.container.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { connect } from 'react-redux'
import { RootDispatch } from '../../types'
import { getState as getModals } from '../../modules/modal/selectors'
import { closeModal } from '../../modules/modal/actions'
import { MapStateProps, MapDispatchProps } from './ModalProvider.types'
import ModalProvider from './ModalProvider'

const mapState = (state: any): MapStateProps => ({
modals: getModals(state)
})

const mapDispatch = (_: RootDispatch): MapDispatchProps => ({})
const mapDispatch = (dispatch: RootDispatch): MapDispatchProps => ({
onClose: (name: string) => dispatch(closeModal(name))
})

export default connect(
mapState,
Expand Down
29 changes: 21 additions & 8 deletions src/providers/ModalProvider/ModalProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
import * as React from 'react'

import { ModalProps } from '../../modules/modal/types'
import { DefaultProps, Props } from './ModalProvider.types'
import { DefaultProps, Props, ModalComponent } from './ModalProvider.types'

export default class ModalProvider extends React.PureComponent<Props> {
static defaultProps: DefaultProps = {
children: null
}

getCloseHandler(name: string) {
return () => this.props.onClose(name)
}

render() {
const { children, components, modals } = this.props

const ModalComponents: JSX.Element[] = []

for (const name in modals) {
const modal = modals[name]
let ModalComponent: React.ComponentType<ModalProps> = components[name]
if (!modal.open) {
continue
}

const Component: ModalComponent = components[name]

if (!ModalComponent) {
if (name) {
throw new Error(`Couldn't find a modal Component named "${name}"`)
}
if (!Component) {
throw new Error(`Couldn't find a modal Component named "${name}"`)
}

ModalComponents.push(<ModalComponent key={name} modal={modal} />)
const onClose = this.getCloseHandler(modal.name)
ModalComponents.push(
<Component
key={name}
name={name}
metadata={modal.metadata}
onClose={onClose}
/>
)
}

return (
Expand Down
14 changes: 11 additions & 3 deletions src/providers/ModalProvider/ModalProvider.types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { ModalState } from '../../modules/modal/reducer'
import { ModalProps } from '../../modules/modal/types'
import { closeModal } from '../../modules/modal/actions'

export type ModalProps = {
name: string
metadata?: any
onClose: () => ReturnType<typeof closeModal>
}
export type ModalComponent = React.ComponentType<ModalProps>

export type DefaultProps = {
children: React.ReactNode | null
}

export type Props = DefaultProps & {
components: Record<string, React.ComponentType<ModalProps>>
components: Record<string, ModalComponent>
modals: ModalState
onClose: typeof closeModal
}

export type MapStateProps = Pick<Props, 'modals'>
export type MapDispatchProps = {}
export type MapDispatchProps = Pick<Props, 'onClose'>

0 comments on commit e3441f7

Please sign in to comment.