自定義組件下拉搜索選擇框

最終效果:

 

代碼如下

interface

export interface IFilter {
    /** unique id */
    id: string;
    /** machine serial number */
    machineSn: string;
    /** if value is true, user select this data  */
    checked: boolean;
}

component

import * as React from "react";
import {IFilters} from "./IFilters";
import "../../public/style/Filter.scss";

interface IFilterProps {
  filters?: IFilters[];
  filterUpdate?: (arrays: IFilters[]) => void;
}
interface IFilterState {
  checkAll: boolean;
  displayDialog: boolean;
  listOfFilters: IFilters[];
  queryInputValue: string;
  metadataList: IFilters[];
}
export class MachineSnFilter extends React.Component<IFilterProps, IFilterState> {
  constructor(props: IFilterProps) {
    super(props);
    this.state = {
      checkAll: false,
      displayDialog: false,
      listOfFilters: [],
      queryInputValue: "",
      metadataList: [],
    };
    this.changeQueryInputValue = this.changeQueryInputValue.bind(this);
    this.checkAllFilters = this.checkAllFilters.bind(this);
    this.cancelOperation = this.cancelOperation.bind(this);
    this.filterOnCheckChange = this.filterOnCheckChange.bind(this);
    this.saveFilters = this.saveFilters.bind(this);
    this.makePropsDataToState = this.makePropsDataToState.bind(this);
  }

  public render() {
    const dialogClassName = this.state.displayDialog
      ? "filter-dialog"
      : "filter-dialog filter-dialog-hidden";
    return (
      <div className="filters">
        {/* switch button */}
        {this.switchButtonRender()}
        {/* dialog */}
        <div className={dialogClassName}>
          {/* check all */}
          <div className="filter-checkAll-label">
            {this.checkAllCheckboxRender()}
            {this.closeIconRender()}
          </div>

          {/* input */}
          {this.queryInputBoxRender()}
          {/* checkbox */}
          <div className="filter-checkbox-part">{this.machineSnFiltersRender()}</div>

          {/* button( save/ cancel ) */}
          <div className="filter-button">
            <button className="filter-button-save" onClick={this.saveFilters}>
              save
            </button>
            <button className="filter-button-cancel" onClick={this.cancelOperation}>
              cancel
            </button>
          </div>
        </div>
      </div>
    );
  }

  public componentDidMount() {
    this.makePropsDataToState();
  }

  public componentDidUpdate(nextProps) {
    if (this.props.filters !== nextProps.filters) {
      this.makePropsDataToState();
    }
  }

  /** when component will un mount, reset all state */
  public componentWillUnmount() {
    if (this.props.filters) {
      const checkAll = this.isCheckAll(this.props.filters);
      this.setState({
        checkAll,
        displayDialog: false,
        listOfFilters: this.props.filters,
        queryInputValue: "",
        metadataList: this.props.filters,
      });
    }
  }

  /** render toggle button */
  private switchButtonRender() {
    return (
      <div
        className="filter-word"
        role="button"
        onClick={() => {
          this.setState({displayDialog: !this.state.displayDialog});
        }}>
        <div>打開</div>
        {/* arrow */}
        <img
          className={
            this.state.displayDialog
              ? "filter-arrow filter-arrowUp"
              : "filter-arrow"
          }
          src={require("../../public/images/arrow.svg")}
          alt="arrow"
        />
      </div>
    );
  }

  /** render checkbox of 'checkAll' */
  private checkAllCheckboxRender() {
    return (
      <>
        <div>
          <img
            className="filter-checkAll-image"
            src={
              this.state.checkAll
                ? require("../../public/images/checkIcon.svg")
                : require("../../public/images/unCheckIcon.svg")
            }
            alt="checkAll icon"
            role="button"
            onClick={this.checkAllFilters}
          />
          <span className="filter-checkAll-word" onClick={this.checkAllFilters}>
            {this.state.checkAll
              ? "uncCheck all"
              : "check all"}
          </span>
        </div>
      </>
    );
  }

  /** render close icon , when click this icon, close dialog */
  private closeIconRender() {
    return (
      <img
        className="filter-closeIcon"
        src={require("../../public/images/closeIcon.svg")}
        alt="close icon"
        role="button"
        onClick={this.cancelOperation}
      />
    );
  }

  /** render query input box */
  private queryInputBoxRender() {
    return (
      <div className="filter-queryInput">
        <input
          type="text"
          className="filter-queryInput-input"
          value={this.state.queryInputValue}
          onChange={this.changeQueryInputValue}
        />
      </div>
    );
  }

  /** render list of machineSn filter items */
  private machineSnFiltersRender() {
    return this.state.listOfFilters.map((e, index) => (
      <div className="filter-checkbox" key={index}>
        <img
          className="filter-checkbox-image"
          src={
            e.checked ? require("../../public/images/checkIcon.svg") : require("../../public/images/unCheckIcon.svg")
          }
          alt="check icon"
          role="button"
          onClick={this.filterOnCheckChange.bind(this, e)}
        />
        <span className="filter-checkbox-word" onClick={this.filterOnCheckChange.bind(this, e)}>
          {e.machineSn}
        </span>
      </div>
    ));
  }

  /** merge props 'filters' to state */
  private makePropsDataToState() {
    const newArr: IFilters[] = [];
    if (this.props.filters) {
      this.props.filters.forEach(item => {
        newArr.push(item);
      });
    }

    const checkAll = this.isCheckAll(newArr);
    this.setState({listOfFilters: newArr, metadataList: newArr, checkAll});
  }

  /** user input field changed, change state 'checkAll','listOfFilters' */
  private changeQueryInputValue(event) {
    const value = event.target.value;
    this.setState({queryInputValue: value}, () => {
      const {metadataList} = {...this.state};
      const newArr = metadataList.filter(item => item.machineSn.includes(value));
      const checkAll = this.isCheckAll(newArr);
      this.setState({
        checkAll,
        listOfFilters: newArr,
      });
    });
  }

  /** make all machineSn filter items are selected, or make all items are unselected */
  private checkAllFilters() {
    this.setState({checkAll: !this.state.checkAll}, () => {
      const newArr: IFilters[] = [];
      this.state.listOfFilters.forEach(item => {
        item = {...item, checked: this.state.checkAll ? true : false};
        newArr.push(item);
      });
      const checkAll = this.isCheckAll(newArr);
      const newSaveItems = this.changeItemsCheckedProperty(this.state.metadataList, newArr);

      this.setState({
        checkAll,
        listOfFilters: newArr,
        metadataList: newSaveItems,
      });
    });
  }

  /**
   * user select machineSn filters, change state 'listOfFilters' and 'checkAll' 'metadata'
   * @param e:`IFilters`  filter item selected by the user
   */
  private filterOnCheckChange(e) {
    const filterArr: IFilters[] = [
      {
        ...e,
        checked: !e.checked,
      },
    ];
    const newListOfItems = this.changeItemsCheckedProperty(this.state.listOfFilters, filterArr);
    const checkAll = this.isCheckAll(newListOfItems);
    const newSaveItems = this.changeItemsCheckedProperty(this.state.metadataList, filterArr);
    this.setState({
      checkAll,
      listOfFilters: newListOfItems,
      metadataList: newSaveItems,
    });
  }

  /**
   * change specified items property of 'checked' in old list, return new list of items
   * @param arr: `IFilters[]` old list of items
   * @param filterArr: `IFilters[]` specified items
   */
  private changeItemsCheckedProperty(arr: IFilters[], filterArr: IFilters[]) {
    const newArr = [...arr];
    for (let i = 0; i < newArr.length; i++) {
      for (let j = 0; j < filterArr.length; j++) {
        let item: IFilters = newArr[i];
        if (newArr[i].id === filterArr[j].id) {
          item = {
            ...newArr[i],
            checked: filterArr[j].checked,
          };
          // replace selected item
          newArr.splice(i, 1, item);
        }
      }
    }
    return newArr;
  }

  /**
   * * if value equal true, there are some item are unselected
   * * if value equal false, all item are selected
   * @param itemList: `IFilters[]` List of data to traverse
   */
  private isCheckAll(itemList: IFilters[]) {
    const unSelectedItems = itemList.filter(e => e.checked === false);
    if (unSelectedItems.length === 0 && itemList.length !== 0) {
      return true;
    }
    return false;
  }

  /** save machineSn filters selected by the user, update metadata, close dialog */
  private saveFilters() {
    if (this.props.filterUpdate) {
      this.props.filterUpdate(this.state.metadataList);
    }
    this.setState({
      displayDialog: false,
      queryInputValue: "",
    });
  }

  /** close dialog of machineSn filters, reset state */
  private cancelOperation() {
    let checkAll = false;
    let listOfFilters: IFilters[] = [];
    let metadataList: IFilters[] = [];
    if (this.props.filters) {
      checkAll = this.isCheckAll(this.props.filters);
      listOfFilters = this.props.filters;
      metadataList = this.props.filters;
    }
    this.setState({
      checkAll,
      displayDialog: false,
      listOfFilters,
      queryInputValue: "",
      metadataList,
    });
  }
}

scss

%filter-input {
  width: 1rem;
  height: 1rem;
  cursor: pointer;
}
%filter-input-label {
  margin-left: 0.6rem;
  cursor: pointer;
}
// flex layout
%filter-flex {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
// button color
$filter-color: #dcdcdc;
// border width
$filter-width: 0.1rem;
.filters {
  margin-top: 2rem;
  position: relative;
  label {
    margin: 0;
  }
  // switch button
  .filter-word {
    cursor: pointer;
    width: 10rem;
    padding: 0.6rem 1rem;
    border: $filter-width solid black;
    font-size: 1.125rem;
    @extend %filter-flex;
    .filter-arrow {
      width: 1.125rem;
      height: 1.125rem;
    }
    .filter-arrowUp {
      transform: rotate(180deg);
    }
  }
  //   dialog
  .filter-dialog {
    display: block;
    background-color: white;
    width: 26rem;
    height: 25rem;
    border: $filter-width solid black;
    position: absolute;
    z-index: 5;
    top: 3.1rem;
    left: 0;
    text-align: center;
    // check all
    .filter-checkAll-label {
      @extend %filter-flex;
      height: 3rem;
      padding: 0 1rem;
      border-bottom: $filter-width solid $filter-color;
      .filter-checkAll-image {
        @extend %filter-input;
      }
      .filter-checkAll-word {
        @extend %filter-input-label;
      }
      // close icon
      .filter-closeIcon {
        display: inline-block;
        width: 2rem;
        height: 2rem;
        cursor: pointer;
      }
    }
    // input
    .filter-queryInput {
      line-height: 3rem;
      text-align: left;
      padding-left: 1rem;
      border-bottom: $filter-width solid $filter-color;
      .filter-queryInput-input {
        height: 2rem;
        outline: none;
        border: $filter-width solid #d9d9d9;
        padding-left: 2rem;
        box-sizing: border-box;
        border-radius: 0.4rem;
        &:hover {
          outline: none;
          border: $filter-width solid #40a9ff;
        }
        &:focus {
          outline: none;
          border: $filter-width solid #40a9ff;
          box-shadow: 0 0 0 0.12rem rgba(24, 144, 255, 0.2);
        }
      }
    }
    // checkbox
    .filter-checkbox-part {
      height: 15rem;
      overflow-y: auto;
      border-bottom: $filter-width solid $filter-color;

      .filter-checkbox {
        display: flex;
        padding: 0.7em 0 0.8rem 1rem;
        align-items: center;
        &:hover {
          background-color: $filter-color;
        }
        .filter-checkbox-image {
          @extend %filter-input;
        }
        .filter-checkbox-word {
          @extend %filter-input-label;
        }
      }
    }
    // button(save/cancel)
    .filter-button {
      margin-top: 1rem;
      .filter-button-save,
      .filter-button-cancel {
        border: 0;
        background-color: $filter-color;
        margin: 0 1rem;
        padding: $filter-width 1rem;
        width: 6rem;
        text-overflow: ellipsis;
        white-space: nowrap;
        overflow: hidden;
        &:hover {
          background-color: #cfcfcf;
        }
      }
    }
  }
  .filter-dialog-hidden {
    display: none;
  }
}

svg

arrow.svg

<svg t="1584599604138" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3233" width="48" height="48"><path d="M512 729.86624c-13.70112 0-27.40224-5.23264-37.84704-15.6672l-328.69376-328.704c-20.91008-20.91008-20.91008-54.80448 0-75.70432 20.89984-20.89984 54.79424-20.89984 75.70432 0L512 600.63744l290.83648-290.83648c20.91008-20.89984 54.80448-20.89984 75.70432 0 20.91008 20.89984 20.91008 54.79424 0 75.70432l-328.69376 328.704C539.40224 724.64384 525.70112 729.86624 512 729.86624z" p-id="3234"></path></svg>

checkIcon.svg

<svg t="1584688047086" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9205" width="48" height="48"><path d="M170.667 0h682.666A170.667 170.667 0 0 1 1024 170.667v682.666A170.667 170.667 0 0 1 853.333 1024H170.667A170.667 170.667 0 0 1 0 853.333V170.667A170.667 170.667 0 0 1 170.667 0z m244.821 661.675L245.333 468.48a42.667 42.667 0 1 0-64 56.32L382.55 753.493a42.667 42.667 0 0 0 63.318 0.854L841.899 327.68a42.667 42.667 0 1 0-62.464-58.027L415.488 661.675z" p-id="9206" fill="#f9b604"></path></svg>

closeIcon.svg

<svg t="1584689872317" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13137" width="48" height="48"><path d="M511.918647 163.154917c-193.438641 0-351.697037 158.254304-351.697037 351.699084 0 193.439664 158.257374 351.697037 351.697037 351.697037 193.394638 0 351.653035-158.25635 351.653035-351.697037C863.570659 321.409221 705.313286 163.154917 511.918647 163.154917M687.736978 641.452327l-49.198515 49.224098-126.619816-126.600373-126.573767 126.600373-49.304939-49.224098 126.636189-126.598326-126.636189-126.600373 49.304939-49.224098 126.573767 126.600373 126.619816-126.600373 49.198515 49.224098-126.573767 126.600373L687.736978 641.452327z" p-id="13138" fill="#dcdcdc"></path></svg>

unCheckIcon.svg

<svg t="1584689369583" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12117" width="48" height="48"><path d="M936.817028 0H87.482913c-47.890646 0-86.983011 38.992384-86.983011 86.983011v849.334115c0 47.890646 38.992384 86.983011 86.983011 86.983011H936.817028c47.890646 0 86.983011-38.992384 86.983011-86.983011V86.883031c-0.09998-47.890646-39.092365-86.883031-86.983011-86.883031z m19.996094 936.217145c0 11.097832-8.998243 20.096075-20.096075 20.096075H87.482913c-11.097832 0-20.096075-8.998243-20.096074-20.096075V86.883031c0-11.097832 8.998243-20.096075 20.096074-20.096075H936.817028c11.097832 0 20.096075 8.998243 20.096075 20.096075v849.334114z" p-id="12118"></path></svg>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章