Sort & Filter
Allow users to rearrange and temporarily remove items from a list of content.
A lengthy list of content may need a way to be controlled by the viewer to reveal wanted content by sorting it in a different order, or by filtering out unneeded items.
Sorting is a method in which the set of content is arranged in a particular order. Commonly this could be alphabetically, or by date. A user might change the sort order to find particular content quickly or to line up content in a specific order to compare them.
const SortExample = () => { const [value, setValue] = React.useState(0); const [open, setOpen] = React.useState(false); const onChangeHandler = (e) => { setValue(e); setOpen(false); }; const sortOptions = [ { text: 'Name: A to Z', value: 0, }, { text: 'Name: Z to A', value: 1, }, ]; const arr = [ 'Alaska', 'Colorado', 'California', 'Michigan', 'New York', ]; const myData = value === 0 ? arr.sort((a, b) => a.localeCompare(b)) : arr.sort((a, b) => b.localeCompare(a)); return ( <Stack direction="column" spacing={2}> <Popover el="span" direction="br" padding="s" onClickOutside={() => setOpen(false)} open={open} portal // This is optional trigger={ <FilterButton onClick={() => setOpen(!open)} value={value !== 0 && sortOptions[value].text} label="Sort" expandIcon={open} /> } > <OptionList options={sortOptions} onChange={onChangeHandler} /> </Popover> <Stack direction="column" spacing={1}> {myData.map((e, i) => <Card padding="thin" key={i}>{e}</Card>)} </Stack> </Stack> ); }; render (SortExample)
A filter hides all content except the what matches the selected criteria. Common filters might be status, time frame, person assigned, category, etc. If a filter is off, that means all content is being shown.
const FilterExample = () => { const [value, setValue] = React.useState([]); const [open, setOpen] = React.useState(false); const onChangeHandler = (data, checked) => { if (checked) { setValue((prevState) => [...prevState, data]); } else { setValue((prevState) => [...prevState].filter((item) => item !== data) ); } }; const multiOptions = [ { text: 'Country', value: 0, }, { text: 'City', value: 1, }, ]; const arr = [ {name: 'Los Angeles', type: 'city'}, {name: 'Mexico', type: 'country'}, {name: 'Seoul', type: 'city'}, {name: 'Spain', type: 'country'}, {name: 'USA', type: 'country'}, ]; return ( <Stack direction="column" spacing={2}> <Popover el="span" direction="br" padding="s" onClickOutside={() => setOpen(false)} open={open} portal // This is optional trigger={ <FilterButton onClick={() => setOpen(!open)} value={value.length > 0 && value.length} label="Type" expandIcon={open} /> } > <OptionList multiple options={multiOptions} onChange={onChangeHandler} value={value} /> </Popover> <Stack direction="column" spacing={1}> {arr.map((e, i) => { const valueArr = value.map((e) => multiOptions[e].text.toLowerCase()); if (valueArr.length === 0) return <Card padding="thin" key={i}>{e.name}</Card> if (valueArr.includes(e.type)) return <Card padding="thin" key={i}>{e.name}</Card> })} </Stack> </Stack> ); }; render (FilterExample)
Sort and filter are powerful when they are combined as they allow the user to redisplay content exactly how they would like to view it.
const SortFilterExample = () => { const [filterValue, setFilterValue] = React.useState([]); const [sortValue, setSortValue] = React.useState(0); const [sortOpen, setSortOpen] = React.useState(false); const [filterOpen, setFilterOpen] = React.useState(false); const filterChangeHandler = (data, checked) => { if (checked) { setFilterValue((prevState) => [...prevState, data]); } else { setFilterValue((prevState) => [...prevState].filter((item) => item !== data) ); } }; const sortChangeHandler = (e) => { setSortValue(e); setSortOpen(false); }; const sortOptions = [ { text: 'Name: A to Z', value: 0, }, { text: 'Name: Z to A', value: 1, }, ]; const multiOptions = [ { text: 'Country', value: 0, }, { text: 'City', value: 1, }, ]; const arr = [ {name: 'Los Angeles', type: 'city'}, {name: 'Mexico', type: 'country'}, {name: 'Seoul', type: 'city'}, {name: 'Spain', type: 'country'}, {name: 'USA', type: 'country'}, ]; const myData = sortValue === 0 ? arr.sort((a, b) => a.name.localeCompare(b.name)) : arr.sort((a, b) => b.name.localeCompare(a.name)); return ( <Stack direction="column" spacing={2}> <Stack spacing={1}> <Popover el="span" direction="br" padding="s" onClickOutside={() => setFilterOpen(false)} open={filterOpen} portal // This is optional trigger={ <FilterButton onClick={() => setFilterOpen(!filterOpen)} value={filterValue.length > 0 && filterValue.length} label="Type" expandIcon={filterOpen} /> } > <OptionList multiple options={multiOptions} onChange={filterChangeHandler} value={filterValue} /> </Popover> <Popover el="span" direction="br" padding="s" onClickOutside={() => setSortOpen(false)} open={sortOpen} portal // This is optional trigger={ <FilterButton onClick={() => setSortOpen(!sortOpen)} value={sortValue !== 0 && sortOptions[sortValue].text} label="Sort" expandIcon={sortOpen} /> } > <OptionList options={sortOptions} onChange={sortChangeHandler} /> </Popover> </Stack> <Stack direction="column" spacing={1}> {myData.map((e, i) => { const valueArr = filterValue.map((e) => multiOptions[e].text.toLowerCase()); if (valueArr.length === 0) return <Card padding="thin" key={i}>{e.name}</Card> if (valueArr.includes(e.type)) return <Card padding="thin" key={i}>{e.name}</Card> })} </Stack> </Stack> ); }; render (SortFilterExample)
A search field used in combination with sort and filter acts as a filter rather than as content discovery.
const SearchTermExample = () => { const [filterValue, setFilterValue] = React.useState([]); const [sortValue, setSortValue] = React.useState(0); const [sortOpen, setSortOpen] = React.useState(false); const [filterOpen, setFilterOpen] = React.useState(false); const [searchTerm, setSearchTerm] = React.useState(''); const searchChangeHandler = (e, d) => { setSearchTerm(d.value); } const filterChangeHandler = (data, checked) => { if (checked) { setFilterValue((prevState) => [...prevState, data]); } else { setFilterValue((prevState) => [...prevState].filter((item) => item !== data) ); } }; const sortChangeHandler = (e) => { setSortValue(e); setSortOpen(false); }; const sortOptions = [ { text: 'Name: A to Z', value: 0, }, { text: 'Name: Z to A', value: 1, }, ]; const multiOptions = [ { text: 'Country', value: 0, }, { text: 'City', value: 1, }, ]; const arr = [ {name: 'Los Angeles', type: 'city'}, {name: 'Mexico', type: 'country'}, {name: 'Seoul', type: 'city'}, {name: 'Spain', type: 'country'}, {name: 'USA', type: 'country'}, ]; const [state, setState] = React.useState(arr); React.useEffect(() => { function filtering() { const valueArr = filterValue.map((e) => multiOptions[e].text.toLowerCase()); if (valueArr.length > 0) { setState(arr.filter((e) => valueArr.includes(e.type) && e.name.toLowerCase().includes(searchTerm.toLowerCase()) )); } else { setState(arr.filter((e) => e.name.toLowerCase().includes(searchTerm.toLowerCase()))) } } filtering() }, [searchTerm, filterValue]); return ( <Stack direction="column" spacing={2}> <Stack spacing={1}> <Input size="small" icon="search" iconPosition="left" onChange={searchChangeHandler}/> <Popover el="span" direction="br" padding="s" onClickOutside={() => setFilterOpen(false)} open={filterOpen} portal // This is optional trigger={ <FilterButton onClick={() => setFilterOpen(!filterOpen)} value={filterValue.length > 0 && filterValue.length} label="Type" expandIcon={filterOpen} /> } > <OptionList multiple options={multiOptions} onChange={filterChangeHandler} value={filterValue} /> </Popover> <Popover el="span" direction="br" padding="s" onClickOutside={() => setSortOpen(false)} open={sortOpen} portal // This is optional trigger={ <FilterButton onClick={() => setSortOpen(!sortOpen)} value={sortValue !== 0 && sortOptions[sortValue].text} label="Sort" expandIcon={sortOpen} /> } > <OptionList options={sortOptions} onChange={sortChangeHandler} /> </Popover> </Stack> <Stack direction="column" spacing={1}> {state.map((e, i) => <Card padding="thin" key={i}>{e.name}</Card>)} </Stack> </Stack> ); }; render (SearchTermExample)
A selectable table should not allow sorting or filtering when an item is selected. The selection can easily be lost.
const TableExample = () => { const [filterValue, setFilterValue] = React.useState([]); const [sortValue, setSortValue] = React.useState(0); const [sortOpen, setSortOpen] = React.useState(false); const [filterOpen, setFilterOpen] = React.useState(false); const [searchTerm, setSearchTerm] = React.useState(''); const arr = [ {name: 'Los Angeles', type: 'city', selected: false}, {name: 'Mexico', type: 'country', selected: false}, {name: 'Seoul', type: 'city', selected: false}, {name: 'Spain', type: 'country', selected: false}, {name: 'USA', type: 'country', selected: false}, ]; const [state, setState] = React.useState(arr); const searchChangeHandler = (e, d) => { setSearchTerm(d.value); } const filterChangeHandler = (data, checked) => { if (checked) { setFilterValue((prevState) => [...prevState, data]); } else { setFilterValue((prevState) => [...prevState].filter((item) => item !== data) ); } }; React.useEffect(() => { function filtering() { const valueArr = filterValue.map((e) => multiOptions[e].text.toLowerCase()); if (valueArr.length > 0) { setState(arr.filter((e) => valueArr.includes(e.type) && e.name.toLowerCase().includes(searchTerm.toLowerCase()) )); } else { setState(arr.filter((e) => e.name.toLowerCase().includes(searchTerm.toLowerCase()))) } } filtering() }, [searchTerm, filterValue]); const sortChangeHandler = (e) => { setSortValue(e); if (e === 0) { setState(prev => prev.sort((a, b) => a.name.localeCompare(b.name))); } else { setState(prev => prev.sort((a, b) => b.name.localeCompare(a.name))); } setSortOpen(false); }; const sortOptions = [ { text: 'Name: A to Z', value: 0, }, { text: 'Name: Z to A', value: 1, }, ]; const multiOptions = [ { text: 'Country', value: 0, }, { text: 'City', value: 1, }, ]; const selectionChange = (event) => setState(prev => prev.map( item => item.name === event.dataItem.name ? {...item, selected: !item.selected} : item )); const headerSelectionChange = (event) => setState(prev => prev.map( item => ({...item, selected: event.syntheticEvent.target.checked}) )); return ( <Stack direction="column" spacing={2}> <Stack justifyContent="space-between" alignContent="center"> { state.every(i => !i.selected) ? ( <Stack spacing={1} style={{height: 32}}> <Input size="small" icon="search" iconPosition="left" onChange={searchChangeHandler}/> <Popover el="span" direction="br" padding="s" onClickOutside={() => setFilterOpen(false)} open={filterOpen} portal // This is optional trigger={ <FilterButton onClick={() => setFilterOpen(!filterOpen)} value={filterValue.length > 0 && filterValue.length} label="Type" expandIcon={filterOpen} /> } > <OptionList multiple options={multiOptions} onChange={filterChangeHandler} value={filterValue} /> </Popover> <Popover el="span" direction="br" padding="s" onClickOutside={() => setSortOpen(false)} open={sortOpen} portal // This is optional trigger={ <FilterButton onClick={() => setSortOpen(!sortOpen)} value={sortValue !== 0 && sortOptions[sortValue].text} label="Sort" expandIcon={sortOpen} /> } > <OptionList options={sortOptions} onChange={sortChangeHandler} /> </Popover> </Stack> ) : <div style={{height: 32}}>{state.filter((e) => e.selected).length} item{state.filter((e) => e.selected).length > 1 && 's'} selected</div> } <Stack> <Button disabled={state.every(i => !i.selected)} onClick={() => alert("Action Triggered")}>Action</Button> </Stack> </Stack> <Stack direction="column" spacing={1}> <Table data={state} total={state.length} selectedField="selected" onSelectionChange={selectionChange} onHeaderSelectionChange={headerSelectionChange} > <TableColumn field="selected" width="50px" // headerSelectionValue={state.every(i => i.selected)} /> <TableColumn field="name" title="Name" /> <TableColumn field="type" title="Type" /> </Table> </Stack> </Stack> ); }; render (TableExample)