Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor <ColumnView> and <Modal> #224

Merged
merged 14 commits into from
Sep 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Breaking
- [Core] [Form] [ImageEditor] Peer dependency changes:
* Change from `@babel/runtime-corejs2` to `@babel/runtime-corejs3`.
- [Core] `<ColumnView>`:
* The `bottomPadding` prop is removed. Please use `bodyPadding` prop and pass an object instead.
- [Core] `<Modal>`:
* `<Modal>` is refactored to render a `<ColumnView>` as its inner layout.
* `<Modal>` no longer takes `size` and `bodyClassName` props.
* The `bodyPadding` prop now takes an object and is passed to `<ColumnView>`.
- [Form] `<SelectList>`:
- Rename prop `values` to `value`, and it receive a single value directly when is not `multiple`, and receive an array when `multiple` is true.
- Rename prop `defaultValues` to `defaultValue`, and it receive a single value directly when is not `multiple`, and receive an array when `multiple` is true.
Expand All @@ -24,13 +30,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Added
- [Core] [Form] [ImageEditor] setup `warning@4.0.3`.
- [Core] Add the `inline-info` icon to the selections of `<Icon>`.
- [Core] Add `flexBody` prop for `<ColumnView>` (and also `<Modal>`) to render its body as a Flexbox.

### Changed
- [Build] Upgrade to Babel v7.4.4 + `core-js` v3 to provide better polyfilling.
- [Build] Upgrade to Lerna v3.16.4; changes publish steps.
- [Core] Update `<Section>` title style and increase bottom margin.
- [Form] Update `<SelectRow>` and `<SwitchRow>` to adpat vertically-reversed appearance as `<TextInputRow>` in v3.0.
- [Form] Add `desc` prop to `<SelectOption>`
- [Storybook] Update examples for refactord `<ColumnView>` and `<Modal>`.

## [3.0.0]
### Breaking
Expand Down
53 changes: 30 additions & 23 deletions packages/core/src/ColumnView.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,57 +15,64 @@ export const BEM = {
footer: ROOT_BEM.element('footer'),
};

export function ColumnPart({ children, ...otherProps }) {
if (!children) {
return null;
}

return <div {...otherProps}>{children}</div>;
}

function ColumnView({
header,
footer,
bottomPadding,
flexBody,
bodyPadding,
// React props
className,
children,
...wrapperProps
}) {
const rootClassName = classNames(BEM.root.toString(), className);
const bodyStyle = {};
const rootClassName = classNames(`${BEM.root}`, className);
const bodyClassName = BEM.body.modifier('flex', flexBody);

if (bottomPadding) {
bodyStyle.paddingBottom = bottomPadding;
}
const bodyStyle = {
paddingTop: bodyPadding.top,
paddingBottom: bodyPadding.bottom,
paddingLeft: bodyPadding.left,
paddingRight: bodyPadding.right,
};

return (
<div className={rootClassName} {...wrapperProps}>
<ColumnPart className={BEM.header.toString()}>
{header}
</ColumnPart>
{header && (
<div className={`${BEM.header}`}>
{header}
</div>
)}

<div className={BEM.body.toString()} style={bodyStyle}>
<div className={`${bodyClassName}`} style={bodyStyle}>
{children}
</div>

<ColumnPart className={BEM.footer.toString()}>
{footer}
</ColumnPart>
{footer && (
<div className={`${BEM.footer}`}>
{footer}
</div>
)}
</div>
);
}

ColumnView.propTypes = {
header: PropTypes.node,
footer: PropTypes.node,
bottomPadding: PropTypes.string,
flexBody: PropTypes.bool,
bodyPadding: PropTypes.shape({
top: PropTypes.number,
bottom: PropTypes.number,
left: PropTypes.number,
right: PropTypes.number,
}),
};

ColumnView.defaultProps = {
header: undefined,
footer: undefined,
bottomPadding: undefined,
flexBody: false,
bodyPadding: { bottom: 24 },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other padding should has default value 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't provide it, it should be undefined and thus it will not be written to CSS.
And thus it would be initial, which happens to equal 0 here.

I think it's fine though.

};

export default ColumnView;
175 changes: 73 additions & 102 deletions packages/core/src/Modal.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import React, {
cloneElement,
isValidElement,
PureComponent
} from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import memoize from 'memoize-one';

import Overlay from './Overlay';
import ColumnView from './ColumnView';
import HeaderRow from './HeaderRow';
import Overlay from './Overlay';
import TextLabel from './TextLabel';

import icBEM from './utils/icBEM';
import prefixClass from './utils/prefixClass';
import wrapIfNotElement from './utils/wrapIfNotElement';
import renderToLayer from './mixins/renderToLayer';

import './styles/_animations.scss';
Expand All @@ -21,126 +21,97 @@ export const MODAL_SIZE = ['small', 'large', 'full'];
export const COMPONENT_NAME = prefixClass('modal');
const ROOT_BEM = icBEM(COMPONENT_NAME);
export const BEM = {
root: ROOT_BEM.modifier('active'),
closable: ROOT_BEM.element('closable'),
root: ROOT_BEM,
container: ROOT_BEM.element('container'),
header: ROOT_BEM.element('header'),
body: ROOT_BEM.element('body')
};

/**
* Render Modal Header
* If string, render <HeaderRow> with label
* If element, render element with headerClassName
*
* @param {String|Node|Any} header - Title string or <HeaderRow>
* @param {String} headerClassName - Header className
* @return {Node|Any} - Header Node or any
*/
function renderHeader(header, headerClassName) {
if (isValidElement(header)) {
return cloneElement(header, {
className: headerClassName
});
}
if (typeof header === 'string') {
const label = <TextLabel align="center" basic={header} />;
return <HeaderRow className={headerClassName} center={label} />;
// -----------------
// Helpers
// -----------------

const createHandleOverlayClick = memoize(
onClose => (event) => {
// Prevent onClick events being propagated to outer modals
event.stopPropagation();
onClose();
}
);

// -----------------
// Sub-component
// -----------------

export function DefaultHeader({ title }) {
const label = <TextLabel align="center" basic={title} />;

return header;
return <HeaderRow center={label} />;
}

export const ModalContent = ({
DefaultHeader.propTypes = {
title: PropTypes.string.isRequired,
};

// -----------------
// Main Component
// -----------------

function Modal({
header,
bodyClassName,
centered,
onClose,
// <ColumnView> props
flexBody,
bodyPadding,
// React props
className,
children,
}) => {
const cNames = classNames(
bodyClassName,
`${BEM.body.modifier('padding', bodyPadding)}`
);
return (
<div className={BEM.container}>
{renderHeader(header, `${BEM.header}`)}
<div
className={cNames}>
{children}
</div>
</div>
);
};
}) {
const rootBem = BEM.root
.modifier('centered', centered)
.toString();

ModalContent.propTypes = {
header: PropTypes.node,
bodyClassName: PropTypes.string,
bodyPadding: PropTypes.bool,
};
const rootClassName = classNames(rootBem, className);

ModalContent.defaultProps = {
header: undefined,
bodyClassName: '',
bodyPadding: false,
};
const headerRow = header && wrapIfNotElement(header, {
with: DefaultHeader,
via: 'title',
});

class Modal extends PureComponent {
handleOverlayClick = (event) => {
const { onClose } = this.props;
// Prevent onClick events being propagated to outer modals
event.stopPropagation();
onClose();
}
const handleOverlayClick = createHandleOverlayClick(onClose);

render() {
const {
size,
header,
bodyClassName,
bodyPadding,
onClose,
centered,
// React props
className,
children,
} = this.props;
const bemClass = BEM.root.modifier('center', centered).modifier(size);
const rootClassName = classNames(bemClass.toString(), className);

return (
<article className={rootClassName}>
<Overlay onClick={this.handleOverlayClick} />
<ModalContent
header={header}
bodyClassName={bodyClassName}
bodyPadding={bodyPadding}
onClose={onClose}>
{children}
</ModalContent>
</article>
);
}
return (
<div className={rootClassName}>
<Overlay onClick={handleOverlayClick} />

<ColumnView
header={headerRow}
className={`${BEM.container}`}
flexBody={flexBody}
bodyPadding={bodyPadding}
>
{children}
</ColumnView>
</div>
);
}

Modal.propTypes = {
size: PropTypes.oneOf(MODAL_SIZE),
onClose: PropTypes.func,
header: ModalContent.propTypes.header,
bodyClassName: ModalContent.propTypes.bodyClassName,
bodyPadding: ModalContent.propTypes.bodyPadding,
header: PropTypes.node,
centered: PropTypes.bool,
onClose: PropTypes.func,
// <ColumnView> props
flexBody: ColumnView.propTypes.flexBody,
bodyPadding: ColumnView.propTypes.bodyPadding,
};

Modal.defaultProps = {
size: undefined,
onClose: () => {},
header: ModalContent.defaultProps.header,
bodyClassName: ModalContent.defaultProps.bodyClassName,
bodyPadding: ModalContent.defaultProps.bodyPadding,
header: undefined,
centered: false,
onClose: () => {},
// <ColumnView> props
flexBody: ColumnView.defaultProps.flexBody,
bodyPadding: ColumnView.defaultProps.bodyPadding,
};

export { Modal as PureModal };

export default renderToLayer(Modal);
Loading