Skip to content

Commit

Permalink
chore(Modal): refactored modal for better customization (#85)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: removed all props in favor of passing in subcomponents
  • Loading branch information
benjitrosch committed Apr 14, 2022
1 parent 75d203a commit a24406c
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 66 deletions.
29 changes: 23 additions & 6 deletions src/Modal/Modal.stories.tsx
Expand Up @@ -10,15 +10,32 @@ export default {
} as Meta

export const Default: Story<ModalProps> = (args) => {
const [visible, toggleVisible] = useState<boolean>(false)
const [visible, setVisible] = useState<boolean>(false)

const toggleVisible = () => {
setVisible(!visible)
}

return (
<>
<Button onClick={() => toggleVisible(!visible)}>Open Modal</Button>
<Modal {...args} open={visible} onCancel={() => toggleVisible(false)}>
Enim dolorem dolorum omnis atque necessitatibus. Consequatur aut
adipisci qui iusto illo eaque. Consequatur repudiandae et. Nulla ea
quasi eligendi. Saepe velit autem minima.
<Button onClick={toggleVisible}>Open Modal</Button>
<Modal {...args} open={visible} onClickBackdrop={toggleVisible}>
<Modal.Header>Lorem Ipsum</Modal.Header>

<Modal.Body>
Enim dolorem dolorum omnis atque necessitatibus. Consequatur aut
adipisci qui iusto illo eaque. Consequatur repudiandae et. Nulla ea
quasi eligendi. Saepe velit autem minima.
</Modal.Body>

<Modal.Actions>
<Button onClick={toggleVisible} color="primary">
Accept
</Button>
<Button onClick={toggleVisible}>
Cancel
</Button>
</Modal.Actions>
</Modal>
</>
)
Expand Down
97 changes: 37 additions & 60 deletions src/Modal/Modal.tsx
@@ -1,97 +1,70 @@
import React, { forwardRef, ReactNode, useImperativeHandle } from 'react'
import React, { forwardRef } from 'react'
import clsx from 'clsx'
import { twMerge } from 'tailwind-merge'

import { IComponentBaseProps } from '../types'

import Button from '../Button'

export type ModalRef = {
accept: () => void
cancel: () => void
}
import ModalActions from './ModalActions'
import ModalBody from './ModalBody'
import ModalHeader from './ModalHeader'

export type ModalProps = React.HTMLAttributes<HTMLDivElement> &
IComponentBaseProps & {
open?: boolean
title?: string
footer?: boolean
acceptText?: string
cancelText?: string
closeOnBlur?: boolean
onAccept?: () => void
onCancel?: () => void
responsive?: boolean
onClickBackdrop?: () => void
}

const Modal = forwardRef<ModalRef, ModalProps>(
const Modal = forwardRef<HTMLDivElement, ModalProps>(
(
{
children,
open,
title,
footer = true,
acceptText = 'Accept',
cancelText = 'Close',
onAccept,
onCancel,
responsive,
onClickBackdrop,
dataTheme,
className,
closeOnBlur = true,
...props
},
ref
): JSX.Element => {
const classes = twMerge(
const containerClasses = twMerge(
'modal',
className,
clsx({
'modal-open': open,
'modal-bottom sm:modal-middle': responsive,
})
)

useImperativeHandle(ref, (): ModalRef => {
return {
accept: () => {
onAccept && onAccept()
},
cancel: () => {
onCancel && onCancel()
},
}
})

const handleBackdropClick: React.MouseEventHandler = (e) => {
if (e.target === e.currentTarget) {
e.stopPropagation()
if (closeOnBlur && onCancel) {
onCancel()
}
}
}
const bodyClasses = twMerge(
'modal-box',
className
)

return (
<div
aria-label="Modal"
aria-hidden={!open}
aria-modal={open}
{...props}
data-theme={dataTheme}
className={classes}
onClick={handleBackdropClick}
className={containerClasses}
onClick={(e) => {
e.stopPropagation()
if (e.target === e.currentTarget) {
e.stopPropagation()
if (onClickBackdrop) {
onClickBackdrop()
}
}
}}
>
<div className="modal-box">
{title ? <div className="w-full mb-8 text-xl">{title}</div> : null}

<div>{children}</div>

{footer ? (
<div className="modal-action">
<Button onClick={onAccept} color="primary">
{acceptText}
</Button>
<Button onClick={onCancel}>{cancelText}</Button>
</div>
) : null}
<div
{...props}
data-theme={dataTheme}
className={bodyClasses}
ref={ref}
>
{children}
</div>
</div>
)
Expand All @@ -100,4 +73,8 @@ const Modal = forwardRef<ModalRef, ModalProps>(

Modal.displayName = 'Modal'

export default Modal
export default Object.assign(Modal, {
Header: ModalHeader,
Body: ModalBody,
Actions: ModalActions,
})
28 changes: 28 additions & 0 deletions src/Modal/ModalActions.tsx
@@ -0,0 +1,28 @@
import React from 'react'
import { twMerge } from 'tailwind-merge'

import { IComponentBaseProps } from '../types'

type ModalActionsProps = React.HTMLAttributes<HTMLDivElement> &
IComponentBaseProps

const ModalActions = React.forwardRef<HTMLDivElement, ModalActionsProps>(({
children,
className,
...props
}, ref) => {
const classes = twMerge('modal-action', className)
return (
<div
{...props}
className={classes}
ref={ref}
>
{children}
</div>
)
})

ModalActions.displayName = "ModalActions"

export default ModalActions
23 changes: 23 additions & 0 deletions src/Modal/ModalBody.tsx
@@ -0,0 +1,23 @@
import React from 'react'

type ModalBodyProps = React.HTMLAttributes<HTMLDivElement>

const ModalBody = React.forwardRef<HTMLDivElement, ModalBodyProps>(({
children,
className,
...props
}, ref) => {
return (
<div
{...props}
className={className}
ref={ref}
>
{children}
</div>
)
})

ModalBody.displayName = "ModalBody"

export default ModalBody
25 changes: 25 additions & 0 deletions src/Modal/ModalHeader.tsx
@@ -0,0 +1,25 @@
import React from 'react'
import { twMerge } from 'tailwind-merge'

type ModalHeaderProps = React.HTMLAttributes<HTMLDivElement>

const ModalHeader = React.forwardRef<HTMLDivElement, ModalHeaderProps>(({
children,
className,
...props
}, ref) => {
const classes = twMerge('w-full mb-8 text-xl', className)
return (
<div
{...props}
className={classes}
ref={ref}
>
{children}
</div>
)
})

ModalHeader.displayName = "ModalHeader"

export default ModalHeader

0 comments on commit a24406c

Please sign in to comment.