preface

📢 Blog debut: Hiro’s blog

Some time ago sent a “front-end development UI public components of the new understanding” essay, after antD some components of the source code to look at, especially the Button component source code, I really knelt, what the f**k, the original can be so designed, less actually can be so used. This is not, reference Antd Button source code, combined with visual interaction, after three times of design review, finally today, the Button component out. Below record their own design and development ideas ~

👌 hope you can bear to see, because this Button component has too many situations, including some pits encountered in the design and development, of course, I this design is not necessarily good, so also hope, you can give some suggestions after reading, thank you

What do you learn after reading this article?

I don’t know what you can harvest, this article mainly recorded in the development of a common components I think some of the design and the hole, if you are like me, want to know how to write a common components, or what to do is to develop a common components, then this article may provide you with a little bit of inspiration ~

The effect

Worried that everyone has been listening to me, to give you an illusion of an armchair, I will first on the renderings. The final implementation is 👇

💨 ButtonIcon and ButtonText were not developed and these two components were not a problem

Design idea

Before I get into the specifics of design and development, let me just say a few words of personal thought: The early stage of the design and evaluation is very important, don’t blindly go hand rolled code, behind closed doors, oneself a person, be sure to before development, you can consider to list all of the conditions and rules, review, put forward the proposal and the team’s members to check as 20 (because the user is you nearby of friend, They will make a lot of unreasonable demands on you.

Component design

Below is the visual draft given by my visual sister, combined with interaction, and finally the Button component is divided into 👇

Usage scenarios

According to usage scenarios, Button components can be divided into:

  • Ordinary button
  • Icon button
  • The text button
  • Combination of buttons
  • Ghost button
  • The white button

According to the type

By type, Button components can be divided into:

  • The main button
  • A button

According to the size

By size, the Button component can be divided into:

  • Small button
  • The standard button
  • Large buttons
  • The thumb button

According to the theme color

By theme color, the Button component can be divided into:

  • Theme button
  • The warning button
  • Dangerous button

Above are all the button divisions I can think of. Once we’re done, we need to confirm some properties. Here are two important ones: Type & Ghost

Here is a small partner to meng, this property is not very simple, why to take this property out of the discussion? Let’s talk about it

Let’s take a look at some of the best UI libraries in the industry, regardless of the intra – group vision, and their definitions of Button type

Ant Design

For the definition of type, only the primary button, secondary button, dashed button, and link button are supported.

In my cognition, for the button color description, is such 👇

  • The blue, dominant color, table buttons are used to indicate meaning.
  • Green, success tone, table button is success
  • Orange, the warning color, the table button is warning, prompt
  • Red, wrong color, table button is wrong, dangerous

Therefore, I am a little confused about Antd. If I look at the Button component of Antd with my cognition, when setting type=primary, it is blue, then naturally, I want danger red, is it right to set type=danger?

✋ Sorry, you think too much

Antd provides a danger attribute that sets the button as a danger button. That is to say, it is right 👇

<Button type="primary" danger /> // RedCopy the code

iView

Let’s look at one of the iView definitions for Button type.

Well, we can see that it seems to have a more consistent definition of type

<Button type="primary" /> // Blue <Button type="info" /> // Light blue <Button type="success" /> // Green <Button type="warning" /> <Button type="danger" /> // RedCopy the code

Element UI

Let’s take a look at how the Element UI is defined. It’s similar to the iView, and it fits our understanding.

Is my Button integrated into type like iView and Element UI, or is it given a separate attribute like Ant Design? Based on this problem, because we still have some different of components, not all like Antd fully open to all developers to use, with a little bit of “characteristics”, so in the first review, with the junior partner in the group discussion, the final decision, given a property called color alone, the theme of the color is decided by the properties button. And type still means one of its types

Again to discuss a thing called ghost, initial, I define it as a “type”, but later, I found that the ghost, it is also influenced by the theme color, such as when you were red, ghost button text color is red, you are green, ghost button text color is green, such as 👇 like this

❗ Here you might ask, isn’t there a field called color that can be used to change the theme color?

Yes, but one of the questions we have to think about is, what are types? Take 🌰 for example, we often ask, “What kind of movies do you like”, you can say thriller, action, speed and other types of movies, but you say, I like good movies, we seriously think, “good”, it belongs to the genre? Does not belong to the genre, “good-looking” is a “attribute” of this film.

Once we’ve defined these properties, there’s nothing to worry about down here. Let’s think about how to develop this component

Design scheme

According to the usage scenario, we can finally define, such as Button, ButtonIcon and ButtonText. Further analysis shows that these three (or even multiple) types of buttons all have some common state properties. Such as size, style, onClick, className and so on, so we can through what way to achieve it?

✋ was originally intended to be designed using inheritance, but as you can see on the React website, Composition VS Inheritance, React recommends using composition rather than inheritance for code reuse between components. React advocates a HOC and combination approach. React wants components to be packaged according to the least usable idea. In OOP principles, this is called the single responsibility principle. In other words, React wants a component to focus on just one thing.

It’s weird to use inheritance here, for example if your code reads 👇

/ / the base class
class BaseButton extends React.Component {}

/ / inheritance
class Button extends BaseButton {}
class ButtonIcon extends BaseButton {}
class ButtonText extends BaseButton {}
Copy the code

It always feels like inheritance is being enforced here. Wouldn’t it be better if we switched to higher-order components?

// High-order components
const BaseButtonHoc = WrapperComponent= > {
    return class extends React.Component{
        return( <React.Fragment> <WrapperComponent {... this.props} /> </React.Fragment> ) } } export default BaseButtonHoc;Copy the code
/ / use
export default BaseButtonHoc(Button);
export default BaseButtonHoc(ButtonIcon);
export default BaseButtonHoc(ButtonText);
Copy the code

Well, it looks like the higher-order component approach is more useful, so go for it

⚠ Note that ButtonGroup is just a container wrapped around a Button; this is not a type derived from BaseButtonHoc.

Development problems

1. Definition of Button style priority

Why did I say at the beginning that you have to list all the rules, because if there are any discrepancies, then sorry, you might have to rewrite the style.

In the case of the ButtonHOC advanced component, I had a problem with my design from the beginning, so let’s look at the code (I know you don’t want to look at a piece of code, I’ll try to minimize it)

Before we look, let’s agree that the Button component accepts the following properties: 👉

parameter instructions
size Size of the button
type Set button type
color Button color, with type
ghost Ghost property to make the button background transparent
antiWhite Anti-white property, suitable for dark background
block An option to adjust the width of the button to its parent width
disabled Button failure state
style Configure the button style
onClick Callback when a button is clicked

And then I wrote a piece of code like this.

Some people here may ask, why are there so many disabled? That’s what I want to make fun of, because that’s what the visual and interactive aspects are about. In other words:

Normally, the color of the different, disabled after the corresponding hover, active, focus is different.

Ghost case, different color, disabled after the corresponding hover, active, focus will be different.

In the case of white, the color is different, disabled after the corresponding hover, active, focus will be different.

And the most uncomfortable is that type, ghost, antiWhite, color these four, can be randomly matched, there are 16 possible, sorry, I urinate. (In fact, the effect of type is only reflected in the antiWhite attribute.)

This is me at the beginning did not define a good style priority pot, their own to their own hole, so the code, at the beginning is good, in the back, after adding different attributes, its performance is obviously not with my expectations (warm remind you, must pay attention to hover, active, focus these problems)

Ant Design Button less: Ant Design Button less: Ant Design Button less I give you an 🌰

<Button color="orange" />
Copy the code

This is a dangerous button, and the corresponding less style might look like 👇.

.@{prefix-button-cls} {
  // The danger button style
  &-danger {
    color: rgba(xxx, xxx, xxx, 1); // Transparency 1 white text
    background-color: rgba(xxx, xxx, xxx, 1); // Opacity 1 for red background
    &:hover {
      color: rgba(xxx, xxx, xxx, 0.8); // Opacity 0.8 white text
      background-color: rgba(xxx, xxx, xxx, 0.7); // Opacity 0.7 red background
    }
    &:active {
      color: rgba(xxx, xxx, xxx, 0.4); // Opacity 0.4 white text
      background-color: rgba(xxx, xxx, xxx, 0.5); // Opacity 0.5 red background}}}Copy the code

This code we actually understand, when we hover, active mouse, transparency will change, and then meet our expectations, at this time, we hand base, added a property ghost

<Button color="orange" ghost={true} />
Copy the code

This is a dangerous ghost button, the corresponding less style might look like 👇 (I will not write the color value, to see the transparency).

.@{prefix-button-cls} {
  // The ghost button
  &-ghost {
    &-danger {
      color: rgba(xxx, xxx, xxx, 1); // Transparency 1 red text
      background-color: rgba(xxx, xxx, xxx, 1); // Opacity 1 on a white background}}}Copy the code

This is also very simple, but we ignore, the corresponding hover, active, focus style should also be modified, otherwise what will happen? The hover, Active, and Focus of the danger ghost button are “danger buttons”, not what we expect.

You think this is the end of it? Let’s base it again and add the disabled attribute. Cool heaven ~~~~

<Button color="danger" ghost={true} disabled={true} />
Copy the code

I don’t need to say it, because disabled, not only the background color, text color changed, and it does not hover, active, focus these interactions brought about by the “style change”, you can understand: button is prohibited, it does not hover, active, focus.

And by this point you should know how to write the style. In the development of this Button component, to my great despair, we are visually different for hover, Active and Focus. In addition to some of them can change the transparency, some of them can change the color directly. Not like Ant Design. Give you extract a small section of source code ~

Then there is a method defined in the global style: colorpalettle.less

Antd is a unified rule, for this kind of transparency can only be changed, we can not, so I can not use this, can only write, one by one. Ok, I’m not going to talk about that, but I’m going to talk about it because I just want you to understand the importance of prioritizing.

After confirming the priority rule: props style > disabled > Ghost > antiWhite > color

Change the code to this, sure enough, whatever attribute, type, everything according to my rules!

Naturally, less code is relatively easy to write.

2. ButtonGroup pit

As we said before, ButtonGroup is just a container around a Button. It is not a type derived from BaseButtonHoc. Hey, I thought the same thing at the time, until I actually did it, and then I realized it was bullshit.

When I first saw it, yes, according to the code, it should be like this, but it’s not what I wanted…

I’m just adding a div to the outside of a Button, so that’s the way it’s supposed to be. That’s the way it’s supposed to be. Under ButtonGroup, remove the border and rounded corners of a Button

// Combine buttons
.@{button-group-prefix-cls} {
  // Remove the border of the Button under the combination
  .@{button-prefix-cls} {
    border: none;
    border-radius: 0; }}Copy the code

With this piece of code, the above situation will not occur. But with that comes a new question, which is, what if I really want rounded corners? Oh, good, you’re like me, we reset it, now add it back.

.@{button-group-prefix-cls} {
  // Reset the border and rounded corners
  &-circle {
    .@{button-prefix-cls} {
      &:first-child {
        border-top-left-radius: @btn-border-radius;
        border-bottom-left-radius: @btn-border-radius;
      }
      &:last-child {
        border-top-right-radius: @btn-border-radius;
        border-bottom-right-radius: @btn-border-radius; }}}}Copy the code

Safe. Think about it. Any other questions? Hey, you still don’t say, there is a big problem, that is, MY incoming Button is big and small how to do!

<Button size="small"> </Button size="large"> </Button> </ButtonGroup>Copy the code

Why? And the first thought that came to me was, I want to rewrite the props size for each Button component. I want to rewrite the props size for each Button component. I want to rewrite the props size for each Button component. Because we are a children toy in ButtonGroup.

class ButtonGroup extends React.PureComponent<AbstrunctButtonGroupProps> {
  renderButtonGroup = ({ getPrefixCls }: ConfigConsumerProps) = > {
    const {
      prefixCls: customizePrefixCls, className, ... .// Do not expand to write
      children
    } = this.props;

    const prefixCls = getPrefixCls('button-group', customizePrefixCls);
    constclasses = classNames(prefixCls, className, ... Do not expand to write);// Render children
    return (
      <div style={style} className={classes}>
        {children}
      </div>
    );
  };

  render() {
    return <ConfigConsumer>{this.renderButtonGroup}</ConfigConsumer>; }}Copy the code

In other words, I don’t care what size you give the Button, I’ll take care of it. Is this code going to end up with a small size

<Button group size="small"> <Button size="large"> Cancel </Button> <Button size="middle"> prompt </Button> <Button </Button> </ButtonGroup>Copy the code

How did you do that? As usual, rewrite the style

.@{button-group-prefix-cls} {
  // Resolve the issue of component size combinations with inconsistent height and width
  &-small {
    .@{button-prefix-cls} {
      min-width: @button-sm-width;
      height: @button-sm-height;
      line-height: @button-sm-height;
      font-size: @button-sm-font-size; }}}Copy the code

ButtonIcon support

Normally, our icon component only needs this to solve 👇

<Button> <Icon />Copy the code

But why did I add a ButtonIcon? Because there is a visual and interactive action: the Icon will change color, including its state will have a strong correlation with your current button. So we can only derive this type of button from ButtonHoc

</ButtonIcon> </ButtonIcon> </ButtonIcon>Copy the code

The pit of ButtonText

I didn’t think it would be a problem, but I’m young. I’m going to correct a little bit here, sorry, but ButtonText is not derived from BaseButtonHoc, so let’s think about it, what’s the difference between a text button and a normal button? Isn’t the border on the outside gone? Is that easy when you think about it?

So how do you do that? You can’t add a property in BaseButtonHoc to determine if it’s a text button, right?

if (!this.props.isText) {
    classes = `has-border` // Normal button
} else {
    classes = `not-border` // The text button
}
Copy the code

Aside from the fact that our BaseButtonHoc is already written, let’s make it clear that ButtonText is a button we’re encapsulating, not a property on the base button.

So how do you do that? It’s the same as ButtonGroup

import Button from './Button'; class ButtonText extends React.PureComponent<AbstrunctButtonTextProps, {}> { renderButtonText = ({ getPrefixCls }: ConfigConsumerProps) => { const { ... } = this.props; const prefixCls = getPrefixCls('button-text', customizePrefixCls); Const classes = classNames(prefixCls, className, {// return ( <div className={classes}> <Button {... this.props}>{children}</Button> </div> ); }; render() { return <ConfigConsumer>{this.renderButtonText}</ConfigConsumer>; }}Copy the code

How do I write the style? It’s easy. When people figure it out, they can do it.

The text component is easy to work with, but the combined text button is more difficult

</ButtonGroup> </ButtonGroup> </ButtonGroup> </ButtonGroup> </ButtonGroup>Copy the code

Actually, that’s not true. ButtonGroup -> ButtonText -> Button ButtonGroup -> ButtonText -> Button

How to deal with it? I’m not going to write it down, just as a question for you to think about, if you’re interested in thinking about how to deal with it, huh

other

For the entire Button component code, I put it here: Button source code, because the article does not want to post too much code, ideas can move ha ~ of course, I more hope that you can go to see the source code, because you read after, you think I write is slag ~ I just reference reference some of the design ideas, low cost of the development of a common component ~

conclusion

Thank you see here, and recently developed some common components, say your feeling, I always wanted to do before a component library, built a wheel, for writing the UI component library, the simplest is to write a Button component, that is “dazed and confused”, feel the Button component that simple, it is best to write components, But now looking back, the simpler things are, the harder they are!!

Before this, I belong to the user, when creating a project, always NPM install UI library, based on the library, a simple second encapsulation, but never go to see its internal implementation principle, until this time, “forced” to see the source code, after seeing, only to find that there is really a gap between people.

Open source is about responsibility. If you make something that wants to be used by more people, that means you have to take more responsibility! Everyone has an open source dream. I also built wheels before, and THIS Vue-Erek-Manage was built from Ant Design Pro. I thought that after I finished this thing and realized the function, IT would be available for everyone to use. My design flaws, my code style, and my skill level cause me to constantly change the code in the framework.

Seems to be far away, ok, don’t nag so much with you, you are skilled people, talk is not responsible, we are responsible to talk, tomorrow morning have to get up to work ~ (quote a shake tone jokes to end, escape…)

A link to the

  • Hiro’s blog