mirror of
https://github.com/gosticks/react-table.git
synced 2025-10-16 11:55:36 +00:00
383 lines
9.3 KiB
JavaScript
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
|