preface

The project is similar to vant’s DropdownMenu: DropdownMenu. Looking at the vans effect, it was not difficult, so I started to build a component like this. The technical stack of the project is React Family bucket + Material UI + Ant Design Mobile.

The effect of vans

I did it myself

Train of thought

  • routine
    Get the DOM element and dynamically modify the innerHtml of the selected DOM. Of course, react doesn't recommend this approachCopy the code
  • I practice
    Since React doesn't recommend manipulating DOM elements directly, dynamic changes can be used insteadclassSuch as:let cls ="normal"<div className={CLS}/> Div className +=" current"
    <div className ={cls}>
    Copy the code

Implementation steps

  • The top TAB is laid out in three divs. Since the title on the TAB needs to be dynamically modified, an array is defined. The TAB data structure in reducer is as follows

    let tabs = {};
    tabs[TABKAY.AREA] = {
    key: TABKAY.AREA,
    text: "All areas".obj: {}}; tabs[TABKAY.SORT] = {key: TABKAY.SORT,
    text: "Comprehensive sort".obj: {}}; tabs[TABKAY.FILTER] = {key: TABKAY.FILTER,
    text: "Screening".obj: SX
    };
    const initialState = {
    areas: [{ id: "".name: "All areas"}].tabs: tabs,
    actionKey: TABKAY.AREA,// Identifies the currently selected TAB
    closePanel: true // Identifies whether the panel div is displayed
    };
    Copy the code
  • Page container rendering methods for tabUI components

    renderTabs() {
    const { tabs, actionKey, closePanel } = this.props;
    //---------
    if(! closePanel) { fixedBody(); }else {
      looseBody();
    }
    //---------
    
    let aray = [];
    for (let key in tabs) {
      let item = tabs[key];
      let cls = item.key + " item";
      if(item.key === actionKey && ! closePanel) { cls +=" current";
      }
    
      aray.push(
        <div className={cls} key={item.key} onClick={()= > this.onChangeTab(item.key)}>
          {item.text}
        </div>);
    }
    
    return aray;
    }
    Copy the code
  • Style: A trick here is to use the pseudo-class of CSS element selectors to cleverly animate arrows and arrows

    .item {
    flex: 1;
    font-size: 15px;
    border-right: 0.5px solid #eaeaea;
    text-align: center;
    
    &:last-child {
      border: none;
    }
    
    &.AREA:after, &.SORT:after, &.FILTER:after {
      content: "";
      display: inline-block;
      width: 5px;
      height: 5px;
      margin-bottom: 2px;
      margin-left: 6px;
      border: 2px solid #Awesome!;
      border-width: 0 2px 2px 0;
      transform: rotate(45deg);
      -webkit-transform: rotate(45deg);
      -webkit-transition: .3s;
      transition: .3s;
    }
    
    &.current {
      color: #0084ff;
    }
    
    &.current:after {
      border-color: #0084ff;
      transform: rotate(225deg);
      -webkit-transform: rotate(225deg);
    }
    Copy the code
  • Chrome View Elements

All regions TAB selected:

The composite TAB is selected

The CSS style “Current” is automatically rendered every time a different TAB is clicked, thus implementing the drop-down menu function.

The complete code

/** * Class: * Author: miyaliunian * Date: 2019/5/26 * Description: tabs */
import React, { Component } from "react";
import { ZHPX, TABKAY } from "@api/Constant";
//Util
import { fixedBody, looseBody } from "@utils/fixRollingPenetration";
//Redux
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { actions as tabActions, getTabs, getAreasList, getActionKey, getClosePanel } from "@reduxs/modules/tabs";
import { actions as hospitalActions,} from "@reduxs/modules/hospital";

/ / style
import "./tabs.less";

class Tabs extends Component {
  
  /** * Changes the state of the currently clicked item while filter requests *@param FilterItem Specifies the currently selected element *@param Key Which TAB is selected */
  changeDoFilter(filterItem, key, event) {
    const { tabActions: { changeFilter }, hospitalActions:{filterHosiContentList} } = this.props;
    event.stopPropagation();
    changeFilter(filterItem, key, (filter) = > {
      filterHosiContentList(filter);
    });
  }

  /** * Filter TAB confirm button *@param event* /
  filterPanel(event) {
    const {tabActions:{closePanelAction}, tabs,hospitalActions:{filterHosiContentList}} = this.props;
    event.stopPropagation();
    closePanelAction(() = >{
      filterHosiContentList(tabs)
    })
  }

  /** * Click to switch Tab *@param key* /
  onChangeTab(key) {
    const { actionKey,tabActions: { changeTab }  } = this.props;
    let closePanel = false;
    // Close panel if the same TAB is clicked before and after
    if (actionKey === key && !this.props.closePanel) {
      closePanel = true;
    }
    closePanel ? looseBody() : fixedBody();
    changeTab(key, closePanel);
  }

  /** * Render top TAB */
  renderTabs() {
    const { tabs, actionKey, closePanel } = this.props;
    //---------
    if(! closePanel) { fixedBody(); }else {
      looseBody();
    }
    //---------

    let aray = [];
    for (let key in tabs) {
      let item = tabs[key];
      let cls = item.key + " item";
      if(item.key === actionKey && ! closePanel) { cls +=" current";
      }

      aray.push(
        <div className={cls} key={item.key} onClick={()= > this.onChangeTab(item.key)}>
          {item.text}
        </div>);
    }

    return aray;
  }

  /** * all regions *@returns {*}* /
  renderAreaContent() {
    const { areasList } = this.props;
    return areasList.map((item, index) = > {
      let cls = item.active + " area-item";
      return (
        <li key={index} className={"area-item"} onClick={(e)= > this.changeDoFilter(item, TABKAY.AREA, e)}>
          {item.name}
        </li>
      );
    });
  }


  /** * all sort *@returns {any[]}* /
  renderSORTContent() {
    let sortList = ZHPX;
    return sortList.map((item, index) = > {
      let cls = item.action ? "type-item active" : "type-item";

      return (
        <li key={index} className={cls} onClick={(e)= > this.changeDoFilter(item, TABKAY.SORT, e)}>
          {item.name}
        </li>
      );
    });
  }


  /** * filter *@returns {*}* /

  renderFilterInnerContent(items/*filterList*/) {
    return items.map((item, index) = > {
      let cls = "cate-box";
      if (item.active) {
        cls += " active";
      }

      return (
        <div key={index} className={cls} onClick={(e)= > this.changeDoFilter(item, TABKAY.FILTER, e)}>
          {item.name}
        </div>
      );
    });

  }

  renderFILTERContent() {
    let filterList = [];
    const { tabs } = this.props;
    filterList = tabs[TABKAY.FILTER].obj;
    return filterList.map((item, index) = > {
      return (
        <li key={index} className={"filter-item"} >
          <p className={"filter-title"} >{index == 0 ? ${item.groupTitle} ':' ${item.groupTitle} '}</p>
          <div className={"item-content"} >
            {this.renderFilterInnerContent(item.items, filterList)}
          </div>
        </li>
      );
    });
  }

  /** * Render filter panel */
  renderContent() {
    const { tabs, actionKey } = this.props;
    let array = [];
    for (let key in tabs) {
      let item = tabs[key];
      let cls = item.key + "-panel";
      if (item.key === actionKey) {
        cls += " current";
      }

      // All regions
      if (item.key === TABKAY.AREA) {
        array.push(
          <ul key={item.key} className={cls}>
            {this.renderAreaContent()}
          </ul>
        );
      } else if (item.key === TABKAY.SORT) {
        // aggregate sort
        array.push(
          <ul key={item.key} className={cls}>
            {this.renderSORTContent()}
          </ul>
        );
      } else if (item.key === TABKAY.FILTER) {
        / / filter
        array.push(
          <ul key={item.key} className={cls}>
            {this.renderFILTERContent()}
            <div className={"filterBtn"} onClick={(e)= >Enclosing filterPanel (e)} > sure</div>
          </ul>); }}return array;
  }


  render() {
    const { closePanel, tabActions: { closePanelAction } } = this.props;
    let cls = "panel";
    if(! closePanel) { cls +=" show";
    } else {
      cls = "panel";
    }
    return (
      <div className={"tab-header"} >
        <div className={"tab-header-top border-bottom"} >
          {this.renderTabs()}
        </div>
        <div className={cls} onClick={()= > closePanelAction(()=>{})}>
          <div className={"panel-inner"} >
            {this.renderContent()}
          </div>
        </div>
      </div>
    );
  }


  componentDidMount() {
    const { areasList, tabActions: { loadAreas } } = this.props;
    if(areasList && areasList.length ! = =1) {
      return; } loadAreas(); }}const mapStateToProps = state= > {
  return {
    areasList: getAreasList(state),
    tabs: getTabs(state),
    actionKey: getActionKey(state),
    closePanel: getClosePanel(state)
  };
};

const mapDispatchToProps = dispatch= > {
  return {
    tabActions: bindActionCreators(tabActions, dispatch),
    hospitalActions: bindActionCreators(hospitalActions, dispatch)
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(Tabs);
Copy the code

conclusion

Welcome to join me on wechat for more experience on react mobile development