1. Project Introduction

Mango UI is a Vue/TypeScript based UI framework that provides buttons, Input, Grid, Layout, Toast, Tabs, Popover, Collapse and other common components. It is suitable for mobile and PC.

2. Project implementation idea (take Button component as an example)

The building of each wheel goes through several stages

  • 1) Requirement analysis (use case diagram, timing diagram, etc.)
  • 2) UI design
  • 3) Write code
  • 4) Testing (unit testing)
  • 5) Document writing (readme.md)
  • 6) Continuous integration

2.1 Requirement Analysis

According to the analysis of the use case diagram, a Button will have the following states: Loading Button, non-clickable Button, hover Button, and pressed Button

2.2 the UI design

Design UI with reference to mainstream frameworks such as Element UI /Ant Design

2.3 Code implementation

2.3.1 Project construction

  • Create a new repository and write a README

Note: Your company’s source code should be on paid Github or Brakets. If it’s on free Github, it’s open source

  • The License can be found by Google “Ruan Yifeng + Software License”. This project uses MIT License
  • runnpm initInitializing the warehouse
  • runnpm i vueSelect the underlying code, Vue for this project
  • A new index. HTML

2.3.2 write components

  • runnpm i -D parcel -bundlerInstall Parcels for packaging and implement single-file components
  • Create a new SRC /app.js, import app.js in index.html, initialize app in app.js, import Vue and component, map component using Compnenets property
  • Create SRC /button.vue, write HTML in template, js and export in script, and SCSS in style
  • Use the mapped component in index.html
  • run.node_modules/.bin/parcelInstall @vue/ Component-compiler-utils, Nod-sass and other tools, configure package.json, and configure the full version of Vue
  • runnpx parcel, compile and run the code
  • Implement text pass value for button: use slot slot
  • Import and use icon: In iconfont determine the appropriate icon, in index.html import JS code using SVG; Or localize SVG: create SRC /svg.js and open and copy the js code of iconfont to introduce svg.js into the components used.
  • To select the icon icon by passing icon, and to select the icon icon’s position relative to the text by passing iconPosition: in button.vue, pass the parameter by means of props
  • Implement property checking to verify that iconPosition is either left or right: pass validator
  • For loading ICONS: @keyFrame + animation
  • Switch between different ICONS when clicking a button: add and listen for the click event
<template> <button class="g-button" :class="{ [`icon-${iconPosition}`]: true }" @click="$emit('click')"> <g-icon class="icon" v-if="icon && ! loading" :name="icon"></g-icon> <g-icon class="loading icon" v-if="loading" name="loading"></g-icon> <div class="content"> <slot /> </div> </button> </template> <script> import Icon from "./icon"; export default { name: "MgButton", components: { "g-icon": Icon, }, props: { icon: {}, iconPosition: { type: String, default: "left", validator(value) { return value === "left" || value === "right"; }, }, loading: { type: Boolean, default: false, }, }, }; </script>Copy the code
  • Implement button merge: Create the buttonGroup component where the slot slot content is the Button component
<template> <div class="g-button-group"> <slot /> </div> </template> <script> export default { name: "MgButtonGroup", mounted() { for (let node of this.$el.children) { let name = node.nodeName.toLowerCase(); if (name ! == "button") {console.warn(' g-button-group children should be all G-buttons, but you wrote ${name} '); }}}}; </script>Copy the code

2.4 Unit Testing

2.4.1 Unit tests and Mocks

  • runnmp i -D chaiInstall Chai and use Chai. Expect to write test cases and assertions
  • Mock tests: Runnmp i -D chai spise, install and listen for the callback function using Chai. Spy,

2.4.2 Use Karma + Mocha for unit tests

  • Install various tools (npm i -D karma karma-chrome-launcher karma-mocha karma-sinon-chai mocha sinon sinon-chai karma-chai karma-chai-spies), create the Karma configuration, create the test/button.test.js file, create the BDD style test script, and run the test script
  • Fact: Karma is a test runner that calls a browser, loads a test script, and then runs a test case. Mocha is a unit testing framework/library that can be used to write test cases, descibe… it… Is the BDD introduced by Mocha; Sinon is a Spy/Stub/mock library to aid testing
  • Knowledge: BDD (Behavior-driven Development); Test-driven Development (TDD)

2.4.3 Continuous integration using Travis CI

  • Create and configure the.travis. Yml file
  • Go to the Travis – Ci website to add the corresponding Github repository
  • Push the.travis. Yml file to the repository

2.4.4 Releasing the NPM package

  • Make sure all the code tests pass
  • Configuration package. Json
  • Upload the code to npmjs.org
  • Mock users use their own packages

The following is the BDD test code

const expect = chai.expect;
import Vue from "vue";
import Button from ".. /src/button";

Vue.config.productionTip = false;
Vue.config.devtools = false;

describe("Button".() = > {
  it("There.".() = > {
    expect(Button).to.be.ok;
  });
  it("Icon can be set.".() = > {
    const Constructor = Vue.extend(Button);
    const vm = new Constructor({
      propsData: {
        icon: "settings",
      },
    }).$mount();
    const useElement = vm.$el.querySelector("use");
    expect(useElement.getAttribute("xlink:href")).to.equal("#i-settings");
    vm.$destroy();
  });
  it("Can set loading.".() = > {
    const Constructor = Vue.extend(Button);
    const vm = new Constructor({
      propsData: {
        icon: "settings".loading: true,
      },
    }).$mount();
    const useElements = vm.$el.querySelectorAll("use");
    expect(useElements.length).to.equal(1);
    expect(useElements[0].getAttribute("xlink:href")).to.equal("#i-loading");
    vm.$destroy();
  });
  it("Icon default order is 1".() = > {
    const div = document.createElement("div");
    document.body.appendChild(div);
    const Constructor = Vue.extend(Button);
    const vm = new Constructor({
      propsData: {
        icon: "settings",
      },
    }).$mount(div);
    const icon = vm.$el.querySelector("svg");
    expect(getComputedStyle(icon).order).to.eq("1");
    vm.$el.remove();
    vm.$destroy();
  });
  it("Setting iconPosition can change the order".() = > {
    const div = document.createElement("div");
    document.body.appendChild(div);
    const Constructor = Vue.extend(Button);
    const vm = new Constructor({
      propsData: {
        icon: "settings".iconPosition: "right",
      },
    }).$mount(div);
    const icon = vm.$el.querySelector("svg");
    expect(getComputedStyle(icon).order).to.eq("2");
    vm.$el.remove();
    vm.$destroy();
  });
  it("Clicking on a button triggers the click event".() = > {
    const Constructor = Vue.extend(Button);
    const vm = new Constructor({
      propsData: {
        icon: "settings",
      },
    }).$mount();

    const callback = sinon.fake();
    vm.$on("click", callback);
    vm.$el.click();
    expect(callback).to.have.been.called;
  });
});
Copy the code

2.5 write the README

UI introduction, NPM standard, continuous integration standard, how to use, browser version adaptation, documentation, questions……

3. Implementation ideas of other component codes

The complete process of building a wheel is covered in Section 2, which covers only the code implementation for each component

3.1 Input component

  • Create SRC /input.vue, write HTML in template, js and export in script, and SCSS in style
  • Implement the passing of the input group price: declare the variable in props, use the variable in input, and write the corresponding style in SCSS
  • Implement the prompt for the error input box by wrapping the prompt with the template label and using v-if=”error”
  • Implement input group events: add change, Input, Focus, blur events, listen for native input change event $event, which triggers our added method, and then write test cases for test-driven development
@change="$emit('change', $event.target.value)"
Copy the code
  • Make input support v-Model
:value="value"
@input="$emit('input', $event.target.value)"
Copy the code

3.2 the Grid components

  • Create SRC /col.vue and SRC /row.vue, write HTML in template, js and export in script, and SCSS in style
  • Implement the difference in the average width of each col per line with the number of cols: use the SCSS loop syntax in the COL component
  • For asymmetrical layouts such as 22:2 width layouts: Set the span parameter in the COL component and bind it to the div
<template>
  <div class="col" :class="[col-${span}]">
    <slot></slot>
  </div>
 </template>
 <style lang="scss" scoped>
.col {
  $class-prefix: col-;
  @for $n from 1 through 24 {
    &.#{$class-prefix}#{$n} {
      width: ($n/24) * 100%;
    }
  }
</style>
Copy the code
  • To achieve an unbalanced layout such as 6+4+14(plate + white space + plate): Set the offset parameter in the COL component and bind it to the div, using SCSS loop syntax for the col margin-left
  • Fixed the issue where the overall layout has left and right padding in the page: Gutter in the props in the row components parameters, for each of the children in the mounted hook to add gutter properties to achieve send ginseng to col components, row in the style of left and right margin set as gutter / 2 px. Col left and right paddinggutter /2 px
  • Refactor your code to increase readability and brevity
  • Use @media to implement the response

3.3 the Layout component

  • Create SRC /layout.vue, SRC /header.vue, SRC /content.vue, SRC /sider. Write js and export in script, and SCSS in style
  • To enable the silder and content to be left and right when the page has a silder element: / / Append a child element to the layout component. / / Append a child element to the layout component. / / Append a child element to the layout component. Add the flex-direction:column to hasSilder
  • Click the button to hide the silder function: add a visible property and click event to the Silder, click event to change the Boolean value of the visible property, with V-if to achieve state switch
  • Wrap the silder with the Transition tag, name=”fade”, and animate the fade in/out animation
.slide-enter-active..slide-leave-active {
  transition: all 0.5 s;
}
.slide-enter..slide-leave-to {
  margin-left: -200px;
}
Copy the code

3.4 Toast components

  • Create SRC/totoast. Vue, write HTML in template, js and export in Script, and SCSS in style
  • Create a new SRC /plugin.js plugin, add a toast attribute to vue. prototype, create an instance, and refer to plugin.js in app.js. In this way, users can choose whether to change vue. prototype or not. If the plugin conflicts, they can choose not to use the plugin
  • Style toast
  • < < autoClose > < / autoClose > < / autoClose > < / autoClose > < / autoClose > < / autoClose > < / autoClose > < / autoClose > < / autoClose > < / autoClose
  • Create a close button and execute the onClickClose event if the user uploads the closeButton
  • To realize the popup function in different positions: accept the position attribute, calculate different class values for different position values, and adopt different animations for approach and exit

3.5 Tab components

  • Vue SRC /tabs. Vue SRC /tabs-head.vue SRC /tabs-item.vue SRC /tabs-body.vue SRC /tabs- Pane.vue Write js and export in script, and SCSS in style
  • When the user clicks on an item, an update event is triggered and selectedTab: Tabs are bound to accept a selected property and use.sync
< tabs :selected.sync="selectedTab"
Copy the code
  • Implement landscape/portrait placement of tabs: Tabs accept a direction attribute, and use the Validator to verify that the attribute can only be landscape or portrait
  • Implement whether TAB is disabled: Tabs -item accepts a disabled property of type Boolean, default false
  • Implementation of tabs: tabs-item publishes events, and tabs provide new Vue to construct eventBus. All descendants of tabs inject dependencies, subscribe events, and trigger corresponding methods.

3.6 Popover components

  • Create SRC /popover. Vue, write HTML in template, js and export in script, and SCSS in style
  • Accept the trigger attribute, and the user fires an event on click or hover to determine whether the popover is visible
  • Accept the position attribute to determine the position at which the popover occurs

3.7 the Collapse of the component

  • Create SRC /collapse. Vue, SRC /collapse-item.vue, create HTML in template, js in script, export, and SCSS in style
  • Determines whether to open the panel: collapse accepts the selected property of the array type. If selected, it will be listened on and the corresponding function will be fired
  • Determines if only one panel is open: Collapse accepts the Boolean sigle attribute; true means only one panel is open at a time
  • When sigle: Collapse implements sigle: Collapse, the new Vue constructs an eventBus, and the stack-item injection dependency triggers the corresponding method. Data flow: Collpase notifying item via EventBus, item notifying EventBus when item is clicked, eventBus then notifying item to close or open