最終效果:
代碼如下
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>