preface
In this ninth article in my series on component design, I’ll introduce you to implementing a lightweight, combinable Modal component that can be found in third-party component libraries such as Antd or elementUI to provide user feedback on a system.
The reason why I can write articles related to component design is that as an excellent front-end engineer, we should not “work hard” step by step in the face of various tedious and repetitive work. Instead, we should sum up a set of our own efficient development methods according to the existing front-end development experience.
[Note] General classification of front-end components:
- Generic components: Button, Icon, etc.
- Layout components: Grid, Layout, etc.
- Navigation components: Breadcrumb, Dropdown, Menu, etc.
- Data entry components: such as form, Switch, Upload file Upload, etc.
- Data presentation components: Avator profile, Table, List, etc.
- Feedback components: Progress bars, drawers, Modal dialog boxes, etc.
- Other Business Types
So when we design the component system, we can refer to the above classification, which is also the classification method of antD, Element, Zend and other major UI libraries.
The body of the
Before starting component design, I hope you have a basic knowledge of CSS3 and JS, and understand the basic React /vue syntax. Let’s deconstruct the Modal component first. A Modal is divided into the following parts:
1. Component design ideas
Following the principles of component design outlined by the author earlier, our first step is to identify requirements. Modal box components typically have the following requirements:
- You can control the style of the Modal body
- Provides a callback after Modal is completely closed
- Can control the cancel button text and style
- Can control the confirmation button text and style
- Controls the placement of modal displays
- Controls whether the close button in the upper right corner is displayed
- You can configure a custom close icon
- Configures whether child elements in Modal are destroyed when closed
- Customize the bottom content of the modal box
- Controls whether keyboard ESC is supported
- Controls whether masks are displayed
- Controls whether clicking on the mask allows closing
- Custom mask styles
- Custom title
- Controls whether the dialog box is visible
- Customize the dialog box width
- Expose the callback to click the mask layer or the upper-right fork or cancel button
- Provide a click OK callback
Once the requirements have been gathered, as an aspiring programmer, the following wireframes emerge:
2. Implement a Modal component based on React
2.1. Modal component framework design
First, let’s write the component framework according to the requirements, so that the business logic will be clearer:
import PropTypes from 'prop-types'
import './index.less'
/** * Modal Modal component * @param {afterClose} func Modal callback after complete shutdown * @param {bodyStyle} Object Modal body style * @param {cancelText} string | ReactNode cancel button text * @ param {centered} bool center display Modal * @ param {closable} bool is show in the top right corner of the close button * @ param {closeIcon} ReactNode custom close icon * @ param {destroyOnClose} bool off the child elements in the destruction of Modal * @ param {footer} null | ReactNode at the bottom of the content, When the default button at the bottom is not needed, This can be set to footer={null} * @param {keyboard} bool Whether esc key exit is supported * @param {mask} bool Whether mask is displayed * @param {maskclosable} bool Click on the mask layer whether to allow close * @ param {maskStyle} object mask style * @ param {okText} string | ReactNode confirm button text * @ param {title} string | ReactNode @param {visible} bool Modal is visible * @param {width} String Modal width * @param {onCancel} func Click mask or cancel button, Or the esc key on the keyboard when the callback * @param {onOk} func click ok callback */
function Modal(props) {
const {
afterClose,
bodyStyle,
cancelText,
centered,
closable,
closeIcon,
destroyOnClose,
footer,
keyboard,
mask,
maskclosable,
maskStyle,
okText,
title,
visible,
width,
onCancel,
onOk
} = props
return <div className="xModalWrap">
<div className="xModalContent">
<div className="xModalHeader">
</div>
<div className="xModalBody">
</div>
<div className="xModalFooter">
</div>
</div>
<div className="xModalMask"></div>
</div>
}
export default Modal
Copy the code
With this framework in place, let’s step by step implement the content inside.
2.2 Basic configuration functions
The basic configuration functions are usually independent of business logic and are used to control the display and hiding of elements. Since they are very easy to implement, let’s implement the following attributes first:
- bodyStyle
- cancelText
- closable
- closeIcon
- footer
- mask
- maskStyle
- okText
- title
- width
These features were partially implemented after the framework was built, because they are relatively simple and do not involve other complex logic. You just expose the properties and use them. The specific implementation is as follows:
// ...
function Modal(props) {
// ...
return <div className="xModalWrap">
<div
className="xModalContent"
style={{
width
}}
>
<div className="xModalHeader">
<div className="xModalTitle">
{ title }
</div>
</div>
{
closable &&
<span className="xModalCloseBtn">
{ closeIcon || <Icon type="FaTimes" /> }
</span>
}
<div className="xModalBody" style={bodyStyle}>
{ children }
</div>
{
footer === null ? null :
<div className="xModalFooter">
{
footer ? footer :
<div className="xFooterBtn">
<Button className="xFooterBtnCancel" type="pure">{ cancelText }</Button>
<Button className="xFooterBtnOk">{ okText }</Button>
</div>
}
</div>
}
</div>
{
mask && <div className="xModalMask" style={maskStyle}></div>
}
</div>
}
Copy the code
With this implementation, it is easy to control a modal component to display exactly which elements and which elements can be turned off as follows:
- Remove footer (by setting footer to NULL)
- Remove the close button in the upper right corner
- Remove mask
2.3 Implementing visible(with popover and hidden animation)
If you are familiar with ANTD or Element, visible is used to control modal display and hide. We will implement the same function here. For hidden and shown animations, we will use Transform: Scale. Let’s look at the implementation first:
let[isHidden, setHidden] = useState(! props.visible)const handleClose = (a)= > {
setHidden(false)}Copy the code
HTML structure is as follows:
<div className="xModalWrap" style={{display: isHidden ? 'none' : 'block'}} >
Copy the code
We know from the above code that the display and hide of the mode box is controlled by setting display: None /block, but we know that display: None is not animatable. To animate the content popover, we use @keyFrame animation, which is also good for backward compatibility with older browsers. The specific CSS code is as follows:
@keyframes xSpread {
0% {
opacity: 0;
transform: scale(0);
}
100% {
opacity: 1;
transform: scale(1); }}Copy the code
2.5 implementation centered
The centered attribute is used to control the distance between the popover content and the whole mask or visual area. The value of “true” means that the popover content is in the center of the mask or visual area. Because our default modal content area is centered left and right and the top is 100px away from the top of the visible area, here we implement the following:
<div className={`xModalContentThe ${centered ? ' xCentered' :"'} `} >
Copy the code
CSS code is as follows:
&.xCentered {
top: 50%;
transform: translateY(-50%);
}
Copy the code
This implementation is also very simple, that is, through attribute centered to dynamically set the class name.
2.6 implementation destroyOnClose
This feature, which means whether or not the child element is removed when the pop-up closes, is described in detail in my post: Implementing a powerful Drawer component with React Portals in React/Vue Component Design. When destroyOnClose is true, we destroy the child elements and rerender the component by maintaining a state. To implement this function, we need to handle the following events:
- When the Close button is clicked, destroy the child component according to destroyOnClose
- When the CONFIRM button is clicked, destroy the child component according to destroyOnClose
- When visible is true, the subcomponent will be rerendered according to destroyOnClose.
// Close event (close and confirm event logic is basically the same, not separately written here)
const handleClose = (a)= > {
setHidden(true)
if(destroyOnClose) {
setDestroyChild(true)}document.body.style.overflow = 'auto'
onCancel && onCancel()
}
// Visivle /destroyOnClose rerenders the child component when updated
useEffect((a)= > {
if(visible) {
if(destroyOnClose) {
setDestroyChild(true)
}
}
}, [visible, destroyOnClose])
Copy the code
This enables us to destroy components when the popover closes.
2.7 Close the Modal box (Modal) when the keyboard key ESC is implemented
For better user physical examination, the author’s Modal component supports keyboard events. We all know that the ESC of the keyboard corresponds to the event code of 27, so we can use this principle to close the Modal box when the keyboard keys ESC:
useEffect((a)= > {
document.onkeydown = function (event) {
let e = event || window.event || arguments.callee.caller.arguments[0]
if (e && e.keyCode === 27) {
handleClose()
}
}
}, [])
Copy the code
Since the event listener only needs to be executed once, the useEffect dependency is set to an empty array. Though this has basically realized the function of the keyboard to close, but such code clearly enough grace, so we have to improve, the method of the keyboard can be closed out, and then in the first callback function useEffect return to another function (the function is component uninstall hooks) before, when we remove the event listener component unloading, This can improve some performance and also help with memory optimization:
const closeModal = function (event) {
let e = event || window.event || arguments.callee.caller.arguments[0]
if (e && e.keyCode === 27) {
handleClose()
}
}
useEffect((a)= > {
document.addEventListener('keydown', closeModal, false)
return (a)= > {
document.removeEventListener('keydown', closeModal, false)}}, [])Copy the code
Isn’t the code and functionality more elegant in this way?
2.8 implementation afterClose
AfterClose executes a callback function after the modal box has closed. We can do this with a class component, because setState can pass two parameters, one is the callback to update state, and the other is the callback to update state. We just need to put afterClose in the callback to update state, which is the second parameter callback. But our modal components are currently written with React hooks and functional components, so how do we implement callbacks after status updates? The author provides an implementation idea here, using closures to achieve, the core code is as follows:
// External to the function component
let hiddenCount = 0;
// Inside the function component
useEffect((a)= > {
if(isHidden && hiddenCount) {
hiddenCount = 0
afterClose && afterClose()
}
hiddenCount = 1
}, [isHidden])
Copy the code
We take advantage of the fact that useEffect can listen not only for the mount component’s hook, but also for state updates. It is important to note that we need to reset hiddenCount before afterClose to avoid the influence of other functions that use modal components.
2.9 Robust support, we use the propTypes tool provided by React:
import PropTypes from 'prop-types'
// ...
Modal.propTypes = {
afterClose: PropTypes.func,
bodyStyle: PropTypes.object,
cancelText: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element
]),
centered: PropTypes.bool,
closable: PropTypes.bool,
closeIcon: PropTypes.element,
destroyOnClose: PropTypes.bool,
footer: PropTypes.oneOfType([
PropTypes.element,
PropTypes.object
]),
keyboard: PropTypes.bool,
mask: PropTypes.bool,
maskclosable: PropTypes.bool,
maskStyle: PropTypes.object,
okText: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element
]),
title: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element
]),
visible: PropTypes.bool,
width: PropTypes.string,
onCancel: PropTypes.func,
onOk: PropTypes.func
}
Copy the code
There are detailed examples of prop-types used on the website. One example is the use of oneOfType to support a component that may be one of multiple types. The complete CSS code of the component is as follows:
.xModalWrap {
position: fixed;
z-index: 999;
top: 0;
left: 0;
width: 100%;
bottom: 0;
overflow: hidden;
.xModalContent {
position: relative;
z-index: 1000;
margin-left: auto;
margin-right: auto;
position: relative;
top: 100px;
background-color: #fff;
background-clip: padding-box;
border-radius: 4px;
-webkit-box-shadow: 0 4px 12px rgba(0.0.0.0.15);
box-shadow: 0 4px 12px rgba(0.0.0.0.15);
pointer-events: auto;
animation: xSpread .3s;
&.xCentered {
top: 50%;
transform: translateY(-50%);
}
.xModalHeader {
padding: 16px 24px;
color: rgba(0.0.0.0.65);
background: #fff;
border-bottom: 1px solid #e8e8e8;
border-radius: 4px 4px 0 0;
.xModalTitle {
margin: 0;
color: rgba(0.0.0.0.85);
font-weight: 500;
font-size: 16px;
line-height: 22px;
word-wrap: break-word; }}.xModalCloseBtn {
position: absolute;
top: 0;
right: 0;
z-index: 10;
padding: 0;
width: 56px;
height: 56px;
color: rgba(0.0.0.0.45);
font-size: 16px;
line-height: 56px;
text-align: center;
text-decoration: none;
background: transparent;
border: 0;
outline: 0;
cursor: pointer;
}
.xModalBody {
padding: 16px 24px;
}
.xModalFooter {
padding: 10px 16px;
text-align: right;
background: transparent;
border-top: 1px solid #e8e8e8;
border-radius: 0 0 4px 4px;
.xFooterBtn {
.xFooterBtnCancel..xFooterBtnOk {
margin-left: 6px;
margin-right: 6px; }}}}.xModalMask {
position: fixed;
z-index: 999;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: rgba(0.0.0.5); }}@keyframes xSpread {
0% {
opacity: 0;
// translateY(-50%) is added to prevent animation wobble
transform: translateY(-50%) scale(0);
}
100% {
opacity: 1;
transform: translateY(-50%) scale(1); }}Copy the code
With these steps, a robust Modal component is completed. Modal components are moderately complex components in the component library. If you don’t understand them, you can ask questions in the comment area, and the author will answer them immediately after seeing them.
2.5 Using Modal Components
We can use it in the following ways:
<Modal title="Xui Basic Popover" centered mask={false} visible={false}>
<p>I'm popover content</p>
<p>I'm popover content</p>
<p>I'm popover content</p>
<p>I'm popover content</p>
</Modal>
Copy the code
I have published the implemented components to NPM. If you are interested, you can directly use NPM after installation. The method is as follows:
npm i @alex_xu/xui
/ / import xui
import {
Button,
Skeleton,
Empty,
Progress,
Tag,
Switch,
Drawer,
Badge,
Alert
} from '@alex_xu/xui'
Copy the code
This component library can be imported on demand. We only need to configure babel-plugin-import in the project. The specific configuration is as follows:
// .babelrc
"plugins": [["import", { "libraryName": "@alex_xu/xui"."style": true}]]Copy the code
The NPM library screenshot is as follows:
The last
The author will continue to implement
- The badge (logo),
- The table (table),
- Tooltip (tooltip bar)
- Skeleton screen,
- Message(global prompt),
- The form (form form),
- The switch (switch),
- Date/calendar,
- Qr code recognizer component
And other components, to repeat the author of many years of componentization journey.
If you are not familiar with react/ Vue component design principles, please refer to my previous component design series:
- Master React/Vue Component Design implements a powerful Drawer component in conjunction with React Portals
- Implement a Tag component and an Empty component in 5 minutes of React/Vue Component Design Mastery
- Master React/Vue Component Design creates materialui-like button click animations with pure CSS and encapsulates react components
- Quickly implement a customizable progress bar component in Master React/Vue Component Design
- Master React/Vue Component Design: Repackaging a real-time preview JSON editor component in JsonEditor (React version)
I have published the component library on NPM, and you can experience the components through the NPM installation.
If you want to get the complete source code of component design series, or want to learn more H5 games, Webpack, node, gulp, CSS3, javascript, nodeJS, Canvas data visualization and other front-end knowledge and practical, welcome to join our technical group in the public number “interesting talk front end” to learn and discuss together, Explore the boundaries of the front end together.
More recommended
- 2 years of vUE project practical experience summary
- Javascript Design Patterns front-end Engineers Need to Know in 15 minutes (with detailed mind maps and source code)
- In 2019, take a look at some of my top questions and advice for job seekers
- A picture shows you how to play vue-Cli3 quickly
- Vue Advanced Advanced series – Play with Vue and vuex in typescript
- “Front-end combat summary” the use of pure CSS website skin and focus diagram switch animation
- “Front-end combat summary” using CSS3 to achieve cool 3D rotation perspective
- Add a loading progress bar to your site using pace. Js
- The Application of design Pattern of “Summary of Front End Actual Combat” — Memorandum Pattern
- “Front End Combat Summary” using postMessage to achieve pluggable cross-domain chatbot
- “Front-end combat summary” of the variable promotion, function declaration promotion and variable scope detailed explanation
- “Front-end combat summary” how to change the URL without refreshing the page