react-table/examples/animated/src/App.js
2019-08-27 09:59:52 -06:00

383 lines
9.3 KiB
JavaScript

import React from 'react'
import styled from 'styled-components'
import { useTable, useSortBy, useFilters, useColumnOrder } from 'react-table'
import { motion, AnimatePresence } from 'framer-motion'
import matchSorter from 'match-sorter'
import makeData from './makeData'
const Styles = styled.div`
padding: 1rem;
table {
border-spacing: 0;
border: 1px solid black;
tr {
:last-child {
td {
border-bottom: 0;
}
}
}
th,
td {
margin: 0;
padding: 0.5rem;
border-bottom: 1px solid black;
border-right: 1px solid black;
background: white;
:last-child {
border-right: 0;
}
}
}
`
// Define a default UI for filtering
function DefaultColumnFilter({
column: { filterValue, preFilteredRows, setFilter },
}) {
const count = preFilteredRows.length
return (
<input
value={filterValue || ''}
onChange={e => {
setFilter(e.target.value || undefined) // Set undefined to remove the filter entirely
}}
placeholder={`Search ${count} records...`}
/>
)
}
// This is a custom filter UI for selecting
// a unique option from a list
function SelectColumnFilter({
column: { filterValue, setFilter, preFilteredRows, id },
}) {
// Calculate the options for filtering
// using the preFilteredRows
const options = React.useMemo(() => {
const options = new Set()
preFilteredRows.forEach(row => {
options.add(row.values[id])
})
return [...options.values()]
}, [id, preFilteredRows])
// Render a multi-select box
return (
<select
value={filterValue}
onChange={e => {
setFilter(e.target.value || undefined)
}}
>
<option value="">All</option>
{options.map((option, i) => (
<option key={i} value={option}>
{option}
</option>
))}
</select>
)
}
// This is a custom filter UI that uses a
// slider to set the filter value between a column's
// min and max values
function SliderColumnFilter({
column: { filterValue, setFilter, preFilteredRows, id },
}) {
// Calculate the min and max
// using the preFilteredRows
const [min, max] = React.useMemo(() => {
let min = preFilteredRows.length ? preFilteredRows[0].values[id] : 0
let max = preFilteredRows.length ? preFilteredRows[0].values[id] : 0
preFilteredRows.forEach(row => {
min = Math.min(row.values[id], min)
max = Math.max(row.values[id], max)
})
return [min, max]
}, [id, preFilteredRows])
return (
<>
<input
type="range"
min={min}
max={max}
value={filterValue || min}
onChange={e => {
setFilter(parseInt(e.target.value, 10))
}}
/>
<button onClick={() => setFilter(undefined)}>Off</button>
</>
)
}
// This is a custom UI for our 'between' or number range
// filter. It uses two number boxes and filters rows to
// ones that have values between the two
function NumberRangeColumnFilter({
column: { filterValue = [], preFilteredRows, setFilter, id },
}) {
const [min, max] = React.useMemo(() => {
let min = preFilteredRows.length ? preFilteredRows[0].values[id] : 0
let max = preFilteredRows.length ? preFilteredRows[0].values[id] : 0
preFilteredRows.forEach(row => {
min = Math.min(row.values[id], min)
max = Math.max(row.values[id], max)
})
return [min, max]
}, [id, preFilteredRows])
return (
<div
style={{
display: 'flex',
}}
>
<input
value={filterValue[0] || ''}
type="number"
onChange={e => {
const val = e.target.value
setFilter((old = []) => [val ? parseInt(val, 10) : undefined, old[1]])
}}
placeholder={`Min (${min})`}
style={{
width: '70px',
marginRight: '0.5rem',
}}
/>
to
<input
value={filterValue[1] || ''}
type="number"
onChange={e => {
const val = e.target.value
setFilter((old = []) => [old[0], val ? parseInt(val, 10) : undefined])
}}
placeholder={`Max (${max})`}
style={{
width: '70px',
marginLeft: '0.5rem',
}}
/>
</div>
)
}
function fuzzyTextFilterFn(rows, id, filterValue) {
return matchSorter(rows, filterValue, { keys: [row => row.values[id]] })
}
// Let the table remove the filter if the string is empty
fuzzyTextFilterFn.autoRemove = val => !val
function shuffle(arr) {
arr = [...arr]
const shuffled = []
while (arr.length) {
const rand = Math.floor(Math.random() * arr.length)
shuffled.push(arr.splice(rand, 1)[0])
}
return shuffled
}
function Table({ columns, data }) {
const defaultColumn = React.useMemo(
() => ({
// Let's set up our default Filter UI
Filter: DefaultColumnFilter,
}),
[]
)
const {
getTableProps,
headerGroups,
rows,
flatColumns,
prepareRow,
setColumnOrder,
state,
} = useTable(
{
columns,
data,
defaultColumn,
},
useColumnOrder,
useFilters,
useSortBy
)
const spring = React.useMemo(
() => ({
type: 'spring',
damping: 50,
stiffness: 100,
}),
[]
)
const randomizeColumns = () => {
setColumnOrder(shuffle(flatColumns.map(d => d.id)))
}
return (
<>
<button onClick={() => randomizeColumns({})}>Randomize Columns</button>
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup, i) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<motion.th
{...column.getHeaderProps({
layoutTransition: spring,
style: {
minWidth: column.minWidth,
},
})}
>
<div {...column.getSortByToggleProps()}>
{column.render('Header')}
<span>
{column.isSorted
? column.isSortedDesc
? ' 🔽'
: ' 🔼'
: ''}
</span>
</div>
<div>{column.canFilter ? column.render('Filter') : null}</div>
</motion.th>
))}
</tr>
))}
</thead>
<tbody>
<AnimatePresence>
{rows.slice(0, 10).map(
(row, i) =>
prepareRow(row) || (
<motion.tr
{...row.getRowProps({
layoutTransition: spring,
exit: { opacity: 0, maxHeight: 0 },
})}
>
{row.cells.map((cell, i) => {
return (
<motion.td
{...cell.getCellProps({
layoutTransition: spring,
})}
>
{cell.render('Cell')}
</motion.td>
)
})}
</motion.tr>
)
)}
</AnimatePresence>
</tbody>
</table>
<pre>
<code>{JSON.stringify(state[0], null, 2)}</code>
</pre>
</>
)
}
// Define a custom filter filter function!
function filterGreaterThan(rows, id, filterValue) {
return rows.filter(row => {
const rowValue = row.values[id]
return rowValue >= filterValue
})
}
// This is an autoRemove method on the filter function that
// when given the new filter value and returns true, the filter
// will be automatically removed. Normally this is just an undefined
// check, but here, we want to remove the filter if it's not a number
filterGreaterThan.autoRemove = val => typeof val !== 'number'
function App() {
const columns = React.useMemo(
() => [
{
Header: 'Name',
columns: [
{
Header: 'First Name',
accessor: 'firstName',
minWidth: 150,
},
{
Header: 'Last Name',
accessor: 'lastName',
minWidth: 150,
// Use our custom `fuzzyText` filter on this column
filter: 'fuzzyText',
},
],
},
{
Header: 'Info',
columns: [
{
Header: 'Age',
accessor: 'age',
minWidth: 150,
Filter: SliderColumnFilter,
filter: 'equals',
},
{
Header: 'Visits',
accessor: 'visits',
minWidth: 150,
Filter: NumberRangeColumnFilter,
filter: 'between',
},
{
Header: 'Status',
accessor: 'status',
minWidth: 150,
Filter: SelectColumnFilter,
filter: 'includes',
},
{
Header: 'Profile Progress',
accessor: 'progress',
minWidth: 150,
Filter: SliderColumnFilter,
filter: filterGreaterThan,
},
],
},
],
[]
)
const data = React.useMemo(() => makeData(100), [])
return (
<Styles>
<Table columns={columns} data={data} />
</Styles>
)
}
export default App