Skip to content

Commit

Permalink
feat(component): restrict actions and header from modals (#209)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Modal is now restricted and uses a `header` and `actions` props instead.

Old:
```jsx
<Modal isOpen={isOpen}>
  <Modal.Header>Modal Title</Modal.Header>
  <Modal.Body>
    <Text>Body content.</Text>
  </Modal.Body>
  <Modal.Actions>
    <Button variant="subtle" onClick={() => setIsOpen(false)}>
       Cancel
     </Button>
  </Modal.Actions>
</Modal>
```

New:
```jsx
<Modal 
  actions={[{text: 'Cancel', variant: 'subtle', onClick: () => setIsOpen(false)}]}
  isOpen={isOpen} 
  header="Modal Title">
  <Text>Body content.</Text>
</Modal>
```
  • Loading branch information
jorgemoya authored Oct 8, 2019
1 parent 463e99f commit bc85d25
Show file tree
Hide file tree
Showing 7 changed files with 403 additions and 130 deletions.
81 changes: 39 additions & 42 deletions packages/big-design/src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import React from 'react';
import { createPortal } from 'react-dom';

import { uniqueId } from '../../utils';
import { Button } from '../Button';
import { H2, HeadingProps } from '../Typography';
import { Button, ButtonProps } from '../Button';
import { H2 } from '../Typography';

import {
StyledModal,
Expand All @@ -16,10 +16,12 @@ import {
} from './styled';

export interface ModalProps {
actions?: ModalAction[];
backdrop: boolean;
isOpen: boolean;
closeOnClickOutside: boolean;
closeOnEscKey: boolean;
header?: string;
isOpen: boolean;
variant: 'modal' | 'dialog';
onClose(): void;
}
Expand All @@ -29,42 +31,20 @@ interface ModalState {
modalContainer: HTMLDivElement | null;
}

export interface ModalActionsProps {
withBorder?: boolean;
}

export interface ModalHeaderProps {
withBorder?: boolean;
export interface ModalAction extends Omit<ButtonProps, 'children'> {
text?: string;
}

const ModalActions: React.FC<ModalActionsProps> = ({ children, withBorder }) => (
<StyledModalActions justifyContent="flex-end" withBorder={withBorder}>
{children}
</StyledModalActions>
);

const ModalHeader: React.FC<ModalHeaderProps> = ({ children, withBorder }) => {
return (
<StyledModalHeader withBorder={withBorder}>
{typeof children === 'string' ? <H2 margin="none">{children}</H2> : children}
</StyledModalHeader>
);
};

export class Modal extends React.PureComponent<ModalProps, ModalState> {
static defaultProps: Partial<ModalProps> = {
backdrop: true,
isOpen: false,
closeOnClickOutside: false,
closeOnEscKey: true,
isOpen: false,
onClose: () => null,
variant: 'modal',
};

static Actions = ModalActions;
static Body = StyledModalBody;
static Header = ModalHeader;

readonly state: ModalState = {
initialBodyOverflowY: '',
modalContainer: null,
Expand Down Expand Up @@ -103,20 +83,22 @@ export class Modal extends React.PureComponent<ModalProps, ModalState> {
}

render() {
const { backdrop, isOpen, variant } = this.props;
const { backdrop, children, isOpen, variant } = this.props;
const { modalContainer } = this.state;

const modalContent = (
<StyledModal
onKeyDown={this.onKeyDown}
backdrop={backdrop}
onClick={this.onClickAway}
onKeyDown={this.onKeyDown}
ref={this.modalRef}
backdrop={backdrop}
variant={variant}
>
<StyledModalContent variant={variant} aria-labelledby={this.headerUniqueId} flexDirection="column">
{this.renderClose()}
{this.renderChildren()}
{this.renderHeader()}
<StyledModalBody>{children}</StyledModalBody>
{this.renderActions()}
</StyledModalContent>
</StyledModal>
);
Expand All @@ -136,18 +118,33 @@ export class Modal extends React.PureComponent<ModalProps, ModalState> {
);
}

private renderChildren() {
const { children } = this.props;
private renderHeader() {
const { header } = this.props;

return (
header &&
typeof header === 'string' && (
<StyledModalHeader id={this.headerUniqueId}>
<H2 margin="none">{header}</H2>
</StyledModalHeader>
)
);
}

return React.Children.map(children, child => {
if (React.isValidElement(child) && child.type === Modal.Header) {
return React.cloneElement(child as React.ReactElement<HeadingProps>, {
id: this.headerUniqueId,
});
}
private renderActions() {
const { actions } = this.props;

return child;
});
return (
Array.isArray(actions) && (
<StyledModalActions justifyContent="flex-end">
{actions.map(({ text, ...props }, index) => (
<Button key={index} {...props}>
{text}
</Button>
))}
</StyledModalActions>
)
);
}

private autoFocus = () => {
Expand Down
Loading

0 comments on commit bc85d25

Please sign in to comment.