mirror of
https://github.com/gosticks/react-table.git
synced 2026-08-19 16:30:21 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bb85b99c3 | ||
|
|
b0d6169848 | ||
|
|
9b7264fad4 | ||
|
|
abe723a87d | ||
|
|
8776d78edb | ||
|
|
f0293f5511 | ||
|
|
16c5512c4f | ||
|
|
3b42c72e7c |
+54
-10
@@ -97,6 +97,19 @@ The following options are supported via the main options object passed to `useTa
|
||||
- Defaults to `initialState`
|
||||
- This key is used to look for the initial state of a row when initializing the `rowState` for a`data` array.
|
||||
- If the value located at `row[initialRowStateKey]` is falsey, `{}` will be used instead.
|
||||
- `getSubRows: Function(row, relativeIndex) => Rows[]`
|
||||
- Optional
|
||||
- Must be **memoized**
|
||||
- Defaults to `(row) => row.subRows || []`
|
||||
- Use this function to change how React Table detects subrows. You could even use this function to generate sub rows if you want.
|
||||
- By default, it will attempt to return the `subRows` property on the row, or an empty array if that is not found.
|
||||
- `getRowID: Function(row, relativeIndex) => string`
|
||||
- Use this function to change how React Table detects unique rows and also how it constructs each row's underlying `path` property.
|
||||
- Optional
|
||||
- Must be **memoized**
|
||||
- Defaults to `(row, relativeIndex) => relativeIndex`
|
||||
- You may want to change this function if
|
||||
- By default, it will use the `index` of the row within it's original array.
|
||||
- `debug: Bool`
|
||||
- Optional
|
||||
- A flag to turn on debug mode.
|
||||
@@ -143,17 +156,24 @@ The following options are supported on any column object you can pass to `column
|
||||
|
||||
The following properties are available on the table instance returned from `useTable`
|
||||
|
||||
- `headers[] Array<Column>`
|
||||
- A **nested** array of final column objects, similar in structure to the original columns configuration option.
|
||||
- See [Column Properties](#column-properties) for more information
|
||||
- `columns: Array<Column>`
|
||||
- A **flat** array of all final column objects computed from the original columns configuration option.
|
||||
- A **nested** array of final column objects, **similar in structure to the original columns configuration option**.
|
||||
- See [Column Properties](#column-properties) for more information
|
||||
- `flatColumns: Array<Column>`
|
||||
- A **flat** array of all final column objects.
|
||||
- See [Column Properties](#column-properties) for more information
|
||||
- `headerGroups: Array<HeaderGroup>`
|
||||
- An array of normalized header groups, each containing a flattened array of final column objects for that row.
|
||||
- **Some of these headers may be materialized as placeholders**
|
||||
- See [Header Group Properties](#headergroup-properties) for more information
|
||||
- `headers: Array<Column>`
|
||||
- An **nested** array of final header objects, **similar in structure to the original columns configuration option, but rebuilt for ordering**
|
||||
- Each contains the headers that are displayed underneath it.
|
||||
- **Some of these headers may be materialized as placeholders**
|
||||
- See [Column Properties](#column-properties) for more information
|
||||
- `flatHeaders[] Array<Column>`
|
||||
- A **flat** array of final header objects found in each header group. Columns may be duplicated (if columns are not adjacent)
|
||||
- A **flat** array of final header objects found in each header group.
|
||||
- **Some of these headers may be materialized as placeholders**
|
||||
- See [Column Properties](#column-properties) for more information
|
||||
- `rows: Array<Row>`
|
||||
- An array of **materialized row objects** from the original `data` array and `columns` passed into the table options
|
||||
@@ -182,7 +202,7 @@ The following properties are available on the table instance returned from `useT
|
||||
- Pass it a valid `rowPath` array, `columnID` and `updater`. The `updater` may be a value or function, similar to `React.useState`'s usage.
|
||||
- If `updater` is a function, it will be passed the previous value
|
||||
|
||||
### `HeaderGroup` Properties
|
||||
### HeaderGroup Properties
|
||||
|
||||
The following additional properties are available on every `headerGroup` object returned by the table instance.
|
||||
|
||||
@@ -477,7 +497,11 @@ function Table({ columns, data }) {
|
||||
<span>
|
||||
{/* Add a sort direction indicator */}
|
||||
<span>
|
||||
{column.isSorted ? (column.isSortedDesc ? ' 🔽' : ' 🔼') : ''}
|
||||
{column.isSorted
|
||||
? column.isSortedDesc
|
||||
? ' 🔽'
|
||||
: ' 🔼'
|
||||
: ''}
|
||||
</span>
|
||||
{/* Add a sort index indicator */}
|
||||
<span>({column.isSorted ? column.sortedIndex + 1 : ''})</span>
|
||||
@@ -921,7 +945,7 @@ The following options are supported via the main options object passed to `useTa
|
||||
- A `pathIndex` can be set as the key and its value set to `true` to expand that row's subRows into view. For example, if `{ '3': true }` was passed as the `expanded` state, the **4th row in the original data array** would be expanded.
|
||||
- For nested expansion, you may **use another object** instead of a Boolean to expand sub rows. For example, if `{ '3': { '5' : true }}` was passed as the `expanded` state, then the **6th subRow of the 4th row and the 4th row of the original data array** would be expanded.
|
||||
- This information is stored in state since the table is allowed to manipulate the filter through user interaction.
|
||||
- `subRowsKey: String`
|
||||
- `getSubRows: Function(row, relativeIndex) => Rows[]`
|
||||
- Optional
|
||||
- See the [useTable hook](#table-options) for more details
|
||||
- `manualExpandedKey: String`
|
||||
@@ -1549,9 +1573,29 @@ The following additional properties are available on every `Cell` object returne
|
||||
- Use this function to programmatically update the state of a cell.
|
||||
- `updater` can be a function or value. If a `function` is passed, it will receive the current value and expect a new one to be returned.
|
||||
|
||||
### Example
|
||||
# `useColumnOrder`
|
||||
|
||||
> Have an example of using useRowState? Submit a PR to add it here!
|
||||
- Plugin Hook
|
||||
- Optional
|
||||
|
||||
`useColumnOrder` is a plugin hook that implements **basic column reordering**. As columns are reordered, their header groups are reverse-engineered so as to never have orphaned header groups.
|
||||
|
||||
### Table Options
|
||||
|
||||
The following options are supported via the main options object passed to `useTable(options)`
|
||||
|
||||
- `state[0].columnOrder: Array<ColumnID>`
|
||||
- Optional
|
||||
- Defaults to `[]`
|
||||
- Any column ID's not represented in this array will be naturally ordered based on their position in the original table's `column` structure
|
||||
|
||||
### Instance Properties
|
||||
|
||||
The following values are provided to the table `instance`:
|
||||
|
||||
- `setColumnOrder: Function(updater: Function | Array<ColumnID>) => void`
|
||||
- Use this function to programmatically update the columnOrder.
|
||||
- `updater` can be a function or value. If a `function` is passed, it will receive the current value and expect a new one to be returned.
|
||||
|
||||
# `useTableState`
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
- Editable Data
|
||||
- [Source + Guide](https://github.com/tannerlinsley/react-table/tree/master/examples/editable-data)
|
||||
- [Open in CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/editable-data)
|
||||
- Column Ordering
|
||||
- [Source + Guide](https://github.com/tannerlinsley/react-table/tree/master/examples/column-ordering)
|
||||
- [Open in CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/column-ordering)
|
||||
- **Complex**
|
||||
- The "Kitchen Sink"
|
||||
- [Source + Guide](https://github.com/tannerlinsley/react-table/tree/master/examples/kitchen-sink)
|
||||
@@ -37,6 +40,9 @@
|
||||
- [Source + Guide](https://github.com/tannerlinsley/react-table/tree/master/examples/pagination-controlled)
|
||||
- [Open in CodeSandobx](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/pagination-controlled)
|
||||
- **UI** - These examples demonstrate how to use React Table with your favorite UI libraries or tools!
|
||||
- Animated (Framer-Motion)
|
||||
- [Source + Guide](https://github.com/tannerlinsley/react-table/tree/master/examples/animated-framer-motion)
|
||||
- [Open in CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/animated-framer-motion)
|
||||
- [ ] Styled-Components
|
||||
- [ ] CSS
|
||||
- [ ] Material-UI
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"presets": ["react-app"],
|
||||
"plugins": ["styled-components"]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
SKIP_PREFLIGHT_CHECK=true
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": ["react-app", "prettier"],
|
||||
"rules": {
|
||||
// "eqeqeq": 0,
|
||||
// "jsx-a11y/anchor-is-valid": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,29 @@
|
||||
const path = require('path')
|
||||
const resolveFrom = require('resolve-from')
|
||||
|
||||
const fixLinkedDependencies = config => {
|
||||
config.resolve = {
|
||||
...config.resolve,
|
||||
alias: {
|
||||
...config.resolve.alias,
|
||||
react$: resolveFrom(path.resolve('node_modules'), 'react'),
|
||||
'react-dom$': resolveFrom(path.resolve('node_modules'), 'react-dom'),
|
||||
},
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
const includeSrcDirectory = config => {
|
||||
config.resolve = {
|
||||
...config.resolve,
|
||||
modules: [path.resolve('src'), ...config.resolve.modules],
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
module.exports = [
|
||||
['use-babel-config', '.babelrc'],
|
||||
['use-eslint-config', '.eslintrc'],
|
||||
fixLinkedDependencies,
|
||||
// includeSrcDirectory,
|
||||
]
|
||||
@@ -0,0 +1,6 @@
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app) and Rescripts.
|
||||
|
||||
You can:
|
||||
|
||||
- [Open this example in a new CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/basic)
|
||||
- `yarn` and `yarn start` to run and edit the example
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "rescripts start",
|
||||
"build": "rescripts build",
|
||||
"test": "rescripts test",
|
||||
"eject": "rescripts eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"framer-motion": "^1.6.3",
|
||||
"match-sorter": "^4.0.1",
|
||||
"namor": "^1.1.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-scripts": "3.0.1",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-table": "next",
|
||||
"styled-components": "^4.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rescripts/cli": "^0.0.11",
|
||||
"@rescripts/rescript-use-babel-config": "^0.0.8",
|
||||
"@rescripts/rescript-use-eslint-config": "^0.0.9",
|
||||
"babel-eslint": "10.0.1"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
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
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import App from './App'
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div')
|
||||
ReactDOM.render(<App />, div)
|
||||
ReactDOM.unmountComponentAtNode(div)
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import './index.css'
|
||||
import App from './App'
|
||||
import * as serviceWorker from './serviceWorker'
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'))
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||
serviceWorker.unregister()
|
||||
@@ -0,0 +1,40 @@
|
||||
import namor from 'namor'
|
||||
|
||||
const range = len => {
|
||||
const arr = []
|
||||
for (let i = 0; i < len; i++) {
|
||||
arr.push(i)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
const newPerson = () => {
|
||||
const statusChance = Math.random()
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numbers: 0 }),
|
||||
lastName: namor.generate({ words: 1, numbers: 0 }),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
visits: Math.floor(Math.random() * 100),
|
||||
progress: Math.floor(Math.random() * 100),
|
||||
status:
|
||||
statusChance > 0.66
|
||||
? 'relationship'
|
||||
: statusChance > 0.33
|
||||
? 'complicated'
|
||||
: 'single',
|
||||
}
|
||||
}
|
||||
|
||||
export default function makeData(...lens) {
|
||||
const makeDataLevel = (depth = 0) => {
|
||||
const len = lens[depth]
|
||||
return range(len).map(d => {
|
||||
return {
|
||||
...newPerson(),
|
||||
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return makeDataLevel()
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// This optional code is used to register a service worker.
|
||||
// register() is not called by default.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on subsequent visits to a page, after all the
|
||||
// existing tabs open on the page have been closed, since previously cached
|
||||
// resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model and instructions on how to
|
||||
// opt-in, read https://bit.ly/CRA-PWA
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
)
|
||||
|
||||
export function register(config) {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||
return
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl, config)
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||
)
|
||||
})
|
||||
} else {
|
||||
// Is not localhost. Just register service worker
|
||||
registerValidSW(swUrl, config)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl, config) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing
|
||||
if (installingWorker == null) {
|
||||
return
|
||||
}
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the updated precached content has been fetched,
|
||||
// but the previous service worker will still serve the older
|
||||
// content until all client tabs are closed.
|
||||
console.log(
|
||||
'New content is available and will be used when all ' +
|
||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||
)
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onUpdate) {
|
||||
config.onUpdate(registration)
|
||||
}
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.')
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onSuccess) {
|
||||
config.onSuccess(registration)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error)
|
||||
})
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl, config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl)
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
const contentType = response.headers.get('content-type')
|
||||
if (
|
||||
response.status === 404 ||
|
||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload()
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl, config)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister()
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"presets": ["react-app"],
|
||||
"plugins": ["styled-components"]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
SKIP_PREFLIGHT_CHECK=true
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": ["react-app", "prettier"],
|
||||
"rules": {
|
||||
// "eqeqeq": 0,
|
||||
// "jsx-a11y/anchor-is-valid": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,29 @@
|
||||
const path = require('path')
|
||||
const resolveFrom = require('resolve-from')
|
||||
|
||||
const fixLinkedDependencies = config => {
|
||||
config.resolve = {
|
||||
...config.resolve,
|
||||
alias: {
|
||||
...config.resolve.alias,
|
||||
react$: resolveFrom(path.resolve('node_modules'), 'react'),
|
||||
'react-dom$': resolveFrom(path.resolve('node_modules'), 'react-dom'),
|
||||
},
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
const includeSrcDirectory = config => {
|
||||
config.resolve = {
|
||||
...config.resolve,
|
||||
modules: [path.resolve('src'), ...config.resolve.modules],
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
module.exports = [
|
||||
['use-babel-config', '.babelrc'],
|
||||
['use-eslint-config', '.eslintrc'],
|
||||
fixLinkedDependencies,
|
||||
// includeSrcDirectory,
|
||||
]
|
||||
@@ -0,0 +1,6 @@
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app) and Rescripts.
|
||||
|
||||
You can:
|
||||
|
||||
- [Open this example in a new CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/basic)
|
||||
- `yarn` and `yarn start` to run and edit the example
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "rescripts start",
|
||||
"build": "rescripts build",
|
||||
"test": "rescripts test",
|
||||
"eject": "rescripts eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"framer-motion": "^1.6.3",
|
||||
"match-sorter": "^4.0.1",
|
||||
"namor": "^1.1.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-scripts": "3.0.1",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-table": "next",
|
||||
"styled-components": "^4.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rescripts/cli": "^0.0.11",
|
||||
"@rescripts/rescript-use-babel-config": "^0.0.8",
|
||||
"@rescripts/rescript-use-eslint-config": "^0.0.9",
|
||||
"babel-eslint": "10.0.1"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
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
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import App from './App'
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div')
|
||||
ReactDOM.render(<App />, div)
|
||||
ReactDOM.unmountComponentAtNode(div)
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import './index.css'
|
||||
import App from './App'
|
||||
import * as serviceWorker from './serviceWorker'
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'))
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||
serviceWorker.unregister()
|
||||
@@ -0,0 +1,40 @@
|
||||
import namor from 'namor'
|
||||
|
||||
const range = len => {
|
||||
const arr = []
|
||||
for (let i = 0; i < len; i++) {
|
||||
arr.push(i)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
const newPerson = () => {
|
||||
const statusChance = Math.random()
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numbers: 0 }),
|
||||
lastName: namor.generate({ words: 1, numbers: 0 }),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
visits: Math.floor(Math.random() * 100),
|
||||
progress: Math.floor(Math.random() * 100),
|
||||
status:
|
||||
statusChance > 0.66
|
||||
? 'relationship'
|
||||
: statusChance > 0.33
|
||||
? 'complicated'
|
||||
: 'single',
|
||||
}
|
||||
}
|
||||
|
||||
export default function makeData(...lens) {
|
||||
const makeDataLevel = (depth = 0) => {
|
||||
const len = lens[depth]
|
||||
return range(len).map(d => {
|
||||
return {
|
||||
...newPerson(),
|
||||
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return makeDataLevel()
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// This optional code is used to register a service worker.
|
||||
// register() is not called by default.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on subsequent visits to a page, after all the
|
||||
// existing tabs open on the page have been closed, since previously cached
|
||||
// resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model and instructions on how to
|
||||
// opt-in, read https://bit.ly/CRA-PWA
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
)
|
||||
|
||||
export function register(config) {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||
return
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl, config)
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||
)
|
||||
})
|
||||
} else {
|
||||
// Is not localhost. Just register service worker
|
||||
registerValidSW(swUrl, config)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl, config) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing
|
||||
if (installingWorker == null) {
|
||||
return
|
||||
}
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the updated precached content has been fetched,
|
||||
// but the previous service worker will still serve the older
|
||||
// content until all client tabs are closed.
|
||||
console.log(
|
||||
'New content is available and will be used when all ' +
|
||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||
)
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onUpdate) {
|
||||
config.onUpdate(registration)
|
||||
}
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.')
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onSuccess) {
|
||||
config.onSuccess(registration)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error)
|
||||
})
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl, config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl)
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
const contentType = response.headers.get('content-type')
|
||||
if (
|
||||
response.status === 404 ||
|
||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload()
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl, config)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister()
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"presets": ["react-app"],
|
||||
"plugins": ["styled-components"]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
SKIP_PREFLIGHT_CHECK=true
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": ["react-app", "prettier"],
|
||||
"rules": {
|
||||
// "eqeqeq": 0,
|
||||
// "jsx-a11y/anchor-is-valid": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,29 @@
|
||||
const path = require('path')
|
||||
const resolveFrom = require('resolve-from')
|
||||
|
||||
const fixLinkedDependencies = config => {
|
||||
config.resolve = {
|
||||
...config.resolve,
|
||||
alias: {
|
||||
...config.resolve.alias,
|
||||
react$: resolveFrom(path.resolve('node_modules'), 'react'),
|
||||
'react-dom$': resolveFrom(path.resolve('node_modules'), 'react-dom'),
|
||||
},
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
const includeSrcDirectory = config => {
|
||||
config.resolve = {
|
||||
...config.resolve,
|
||||
modules: [path.resolve('src'), ...config.resolve.modules],
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
module.exports = [
|
||||
['use-babel-config', '.babelrc'],
|
||||
['use-eslint-config', '.eslintrc'],
|
||||
fixLinkedDependencies,
|
||||
// includeSrcDirectory,
|
||||
]
|
||||
@@ -0,0 +1,6 @@
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app) and Rescripts.
|
||||
|
||||
You can:
|
||||
|
||||
- [Open this example in a new CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/basic)
|
||||
- `yarn` and `yarn start` to run and edit the example
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "rescripts start",
|
||||
"build": "rescripts build",
|
||||
"test": "rescripts test",
|
||||
"eject": "rescripts eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"framer-motion": "^1.6.3",
|
||||
"match-sorter": "^4.0.1",
|
||||
"namor": "^1.1.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-scripts": "3.0.1",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-table": "next",
|
||||
"styled-components": "^4.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rescripts/cli": "^0.0.11",
|
||||
"@rescripts/rescript-use-babel-config": "^0.0.8",
|
||||
"@rescripts/rescript-use-eslint-config": "^0.0.9",
|
||||
"babel-eslint": "10.0.1"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
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 {
|
||||
getTableProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
flatColumns,
|
||||
prepareRow,
|
||||
setColumnOrder,
|
||||
state,
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
},
|
||||
useColumnOrder
|
||||
)
|
||||
|
||||
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 => (
|
||||
<th {...column.getHeaderProps()}>{column.render('Header')}</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody>
|
||||
<AnimatePresence>
|
||||
{rows.slice(0, 10).map(
|
||||
(row, i) =>
|
||||
prepareRow(row) || (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map((cell, i) => {
|
||||
return (
|
||||
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</tbody>
|
||||
</table>
|
||||
<pre>
|
||||
<code>{JSON.stringify(state[0], null, 2)}</code>
|
||||
</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function App() {
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'Name',
|
||||
columns: [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
Header: 'Info',
|
||||
columns: [
|
||||
{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
},
|
||||
{
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
},
|
||||
{
|
||||
Header: 'Profile Progress',
|
||||
accessor: 'progress',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
const data = React.useMemo(() => makeData(10), [])
|
||||
|
||||
return (
|
||||
<Styles>
|
||||
<Table columns={columns} data={data} />
|
||||
</Styles>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import App from './App'
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div')
|
||||
ReactDOM.render(<App />, div)
|
||||
ReactDOM.unmountComponentAtNode(div)
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import './index.css'
|
||||
import App from './App'
|
||||
import * as serviceWorker from './serviceWorker'
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'))
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||
serviceWorker.unregister()
|
||||
@@ -0,0 +1,40 @@
|
||||
import namor from 'namor'
|
||||
|
||||
const range = len => {
|
||||
const arr = []
|
||||
for (let i = 0; i < len; i++) {
|
||||
arr.push(i)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
const newPerson = () => {
|
||||
const statusChance = Math.random()
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numbers: 0 }),
|
||||
lastName: namor.generate({ words: 1, numbers: 0 }),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
visits: Math.floor(Math.random() * 100),
|
||||
progress: Math.floor(Math.random() * 100),
|
||||
status:
|
||||
statusChance > 0.66
|
||||
? 'relationship'
|
||||
: statusChance > 0.33
|
||||
? 'complicated'
|
||||
: 'single',
|
||||
}
|
||||
}
|
||||
|
||||
export default function makeData(...lens) {
|
||||
const makeDataLevel = (depth = 0) => {
|
||||
const len = lens[depth]
|
||||
return range(len).map(d => {
|
||||
return {
|
||||
...newPerson(),
|
||||
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return makeDataLevel()
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// This optional code is used to register a service worker.
|
||||
// register() is not called by default.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on subsequent visits to a page, after all the
|
||||
// existing tabs open on the page have been closed, since previously cached
|
||||
// resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model and instructions on how to
|
||||
// opt-in, read https://bit.ly/CRA-PWA
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
)
|
||||
|
||||
export function register(config) {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||
return
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl, config)
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||
)
|
||||
})
|
||||
} else {
|
||||
// Is not localhost. Just register service worker
|
||||
registerValidSW(swUrl, config)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl, config) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing
|
||||
if (installingWorker == null) {
|
||||
return
|
||||
}
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the updated precached content has been fetched,
|
||||
// but the previous service worker will still serve the older
|
||||
// content until all client tabs are closed.
|
||||
console.log(
|
||||
'New content is available and will be used when all ' +
|
||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||
)
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onUpdate) {
|
||||
config.onUpdate(registration)
|
||||
}
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.')
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onSuccess) {
|
||||
config.onSuccess(registration)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error)
|
||||
})
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl, config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl)
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
const contentType = response.headers.get('content-type')
|
||||
if (
|
||||
response.status === 404 ||
|
||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload()
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl, config)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister()
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,7 +42,7 @@ function Table({ columns: userColumns, data, renderRowSubComponent }) {
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
columns,
|
||||
flatColumns,
|
||||
state: [{ expanded }],
|
||||
} = useTable(
|
||||
{
|
||||
@@ -87,7 +87,7 @@ function Table({ columns: userColumns, data, renderRowSubComponent }) {
|
||||
*/}
|
||||
{row.isExpanded ? (
|
||||
<tr>
|
||||
<td colSpan={columns.length}>
|
||||
<td colSpan={flatColumns.length}>
|
||||
{/*
|
||||
Inside it, call our renderRowSubComponent function. In reality,
|
||||
you coul pass whatever you want as props to
|
||||
|
||||
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-table",
|
||||
"version": "7.0.0-alpha.27",
|
||||
"version": "7.0.0-alpha.31",
|
||||
"description": "A fast, lightweight, opinionated table and datagrid built on React",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/tannerlinsley/react-table#readme",
|
||||
|
||||
+79
-79
@@ -8,9 +8,8 @@ import {
|
||||
flexRender,
|
||||
decorateColumnTree,
|
||||
makeHeaderGroups,
|
||||
findMaxDepth,
|
||||
flattenBy,
|
||||
determineColumnVisibility,
|
||||
determineHeaderVisibility,
|
||||
} from '../utils'
|
||||
|
||||
import { useTableState } from './useTableState'
|
||||
@@ -18,14 +17,10 @@ import { useTableState } from './useTableState'
|
||||
const propTypes = {
|
||||
// General
|
||||
data: PropTypes.array.isRequired,
|
||||
columns: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
Cell: PropTypes.any,
|
||||
Header: PropTypes.any,
|
||||
})
|
||||
).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
defaultColumn: PropTypes.object,
|
||||
subRowsKey: PropTypes.string,
|
||||
getSubRows: PropTypes.func,
|
||||
getRowID: PropTypes.func,
|
||||
debug: PropTypes.bool,
|
||||
}
|
||||
|
||||
@@ -34,6 +29,9 @@ const renderErr =
|
||||
|
||||
const defaultColumnInstance = {}
|
||||
|
||||
const defaultGetSubRows = (row, index) => row.subRows || []
|
||||
const defaultGetRowID = (row, index) => index
|
||||
|
||||
export const useTable = (props, ...plugins) => {
|
||||
// Validate props
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'useTable')
|
||||
@@ -44,7 +42,8 @@ export const useTable = (props, ...plugins) => {
|
||||
state: userState,
|
||||
columns: userColumns,
|
||||
defaultColumn = defaultColumnInstance,
|
||||
subRowsKey = 'subRows',
|
||||
getSubRows = defaultGetSubRows,
|
||||
getRowID = defaultGetRowID,
|
||||
debug,
|
||||
} = props
|
||||
|
||||
@@ -89,22 +88,20 @@ export const useTable = (props, ...plugins) => {
|
||||
console.timeEnd('plugins')
|
||||
|
||||
// Decorate All the columns
|
||||
let headers = React.useMemo(
|
||||
let columns = React.useMemo(
|
||||
() => decorateColumnTree(userColumns, defaultColumn),
|
||||
[defaultColumn, userColumns]
|
||||
)
|
||||
|
||||
// Get the flat list of all columns
|
||||
let columns = React.useMemo(() => flattenBy(headers, 'columns'), [headers])
|
||||
|
||||
// Allow hooks to decorate columns (and trigger this memoization via deps)
|
||||
columns = React.useMemo(() => {
|
||||
// Get the flat list of all columns andllow hooks to decorate
|
||||
// those columns (and trigger this memoization via deps)
|
||||
let flatColumns = React.useMemo(() => {
|
||||
if (process.env.NODE_ENV === 'development' && debug)
|
||||
console.time('hooks.columnsBeforeHeaderGroups')
|
||||
|
||||
let newColumns = applyHooks(
|
||||
instanceRef.current.hooks.columnsBeforeHeaderGroups,
|
||||
columns,
|
||||
flattenBy(columns, 'columns'),
|
||||
instanceRef.current
|
||||
)
|
||||
|
||||
@@ -124,12 +121,15 @@ export const useTable = (props, ...plugins) => {
|
||||
|
||||
// Make the headerGroups
|
||||
const headerGroups = React.useMemo(
|
||||
() => makeHeaderGroups(columns, findMaxDepth(headers), defaultColumn),
|
||||
[columns, defaultColumn, headers]
|
||||
() => makeHeaderGroups(flatColumns, columns, defaultColumn),
|
||||
[columns, defaultColumn, flatColumns]
|
||||
)
|
||||
|
||||
const headers = React.useMemo(() => headerGroups[0].headers, [headerGroups])
|
||||
|
||||
Object.assign(instanceRef.current, {
|
||||
columns,
|
||||
flatColumns,
|
||||
headerGroups,
|
||||
headers,
|
||||
})
|
||||
@@ -147,18 +147,20 @@ export const useTable = (props, ...plugins) => {
|
||||
// Keep the original reference around
|
||||
const original = originalRow
|
||||
|
||||
const rowID = getRowID(originalRow, i)
|
||||
|
||||
// Make the new path for the row
|
||||
const path = [...parentPath, i]
|
||||
const path = [...parentPath, rowID]
|
||||
|
||||
flatRows++
|
||||
rowPaths.push(path.join('.'))
|
||||
|
||||
// Process any subRows
|
||||
const subRows = originalRow[subRowsKey]
|
||||
? originalRow[subRowsKey].map((d, i) =>
|
||||
accessRow(d, i, depth + 1, path)
|
||||
)
|
||||
: []
|
||||
let subRows = getSubRows(originalRow, i)
|
||||
|
||||
if (subRows) {
|
||||
subRows = subRows.map((d, i) => accessRow(d, i, depth + 1, path))
|
||||
}
|
||||
|
||||
const row = {
|
||||
original,
|
||||
@@ -183,7 +185,7 @@ export const useTable = (props, ...plugins) => {
|
||||
|
||||
// Create the cells and values
|
||||
row.values = {}
|
||||
columns.forEach(column => {
|
||||
flatColumns.forEach(column => {
|
||||
row.values[column.id] = column.accessor
|
||||
? column.accessor(originalRow, i, { subRows, depth, data })
|
||||
: undefined
|
||||
@@ -197,14 +199,14 @@ export const useTable = (props, ...plugins) => {
|
||||
if (process.env.NODE_ENV === 'development' && debug)
|
||||
console.timeEnd('getAccessedRows')
|
||||
return [accessedData, rowPaths, flatRows]
|
||||
}, [debug, data, columns, subRowsKey])
|
||||
}, [debug, data, getRowID, getSubRows, flatColumns])
|
||||
|
||||
instanceRef.current.rows = rows
|
||||
instanceRef.current.rowPaths = rowPaths
|
||||
instanceRef.current.flatRows = flatRows
|
||||
|
||||
// Determine column visibility
|
||||
determineColumnVisibility(instanceRef.current)
|
||||
determineHeaderVisibility(instanceRef.current)
|
||||
|
||||
// Provide a flat header list for utilities
|
||||
instanceRef.current.flatHeaders = headerGroups.reduce(
|
||||
@@ -258,15 +260,15 @@ export const useTable = (props, ...plugins) => {
|
||||
instanceRef.current.headerGroups.forEach((headerGroup, i) => {
|
||||
// Filter out any headers and headerGroups that don't have visible columns
|
||||
headerGroup.headers = headerGroup.headers.filter(header => {
|
||||
const recurse = columns =>
|
||||
columns.filter(column => {
|
||||
if (column.columns) {
|
||||
return recurse(column.columns)
|
||||
const recurse = headers =>
|
||||
headers.filter(header => {
|
||||
if (header.headers) {
|
||||
return recurse(header.headers)
|
||||
}
|
||||
return column.isVisible
|
||||
return header.isVisible
|
||||
}).length
|
||||
if (header.columns) {
|
||||
return recurse(header.columns)
|
||||
if (header.headers) {
|
||||
return recurse(header.headers)
|
||||
}
|
||||
return header.isVisible
|
||||
})
|
||||
@@ -316,53 +318,51 @@ export const useTable = (props, ...plugins) => {
|
||||
props
|
||||
)
|
||||
|
||||
const visibleColumns = instanceRef.current.columns.filter(
|
||||
column => column.isVisible
|
||||
)
|
||||
|
||||
// Build the cells for each row
|
||||
row.cells = visibleColumns.map(column => {
|
||||
const cell = {
|
||||
column,
|
||||
row,
|
||||
value: row.values[column.id],
|
||||
}
|
||||
|
||||
// Give each cell a getCellProps base
|
||||
cell.getCellProps = props => {
|
||||
const columnPathStr = [...row.path, column.id].join('_')
|
||||
return mergeProps(
|
||||
{
|
||||
key: ['cell', columnPathStr].join('_'),
|
||||
},
|
||||
applyPropHooks(
|
||||
instanceRef.current.hooks.getCellProps,
|
||||
cell,
|
||||
instanceRef.current
|
||||
),
|
||||
props
|
||||
)
|
||||
}
|
||||
|
||||
// Give each cell a renderer function (supports multiple renderers)
|
||||
cell.render = (type, userProps = {}) => {
|
||||
const Comp = typeof type === 'string' ? column[type] : type
|
||||
|
||||
if (typeof Comp === 'undefined') {
|
||||
throw new Error(renderErr)
|
||||
}
|
||||
|
||||
return flexRender(Comp, {
|
||||
...instanceRef.current,
|
||||
// Build the visible cells for each row
|
||||
row.cells = instanceRef.current.flatColumns
|
||||
.filter(d => d.isVisible)
|
||||
.map(column => {
|
||||
const cell = {
|
||||
column,
|
||||
row,
|
||||
cell,
|
||||
...userProps,
|
||||
})
|
||||
}
|
||||
value: row.values[column.id],
|
||||
}
|
||||
|
||||
return cell
|
||||
})
|
||||
// Give each cell a getCellProps base
|
||||
cell.getCellProps = props => {
|
||||
const columnPathStr = [...row.path, column.id].join('_')
|
||||
return mergeProps(
|
||||
{
|
||||
key: ['cell', columnPathStr].join('_'),
|
||||
},
|
||||
applyPropHooks(
|
||||
instanceRef.current.hooks.getCellProps,
|
||||
cell,
|
||||
instanceRef.current
|
||||
),
|
||||
props
|
||||
)
|
||||
}
|
||||
|
||||
// Give each cell a renderer function (supports multiple renderers)
|
||||
cell.render = (type, userProps = {}) => {
|
||||
const Comp = typeof type === 'string' ? column[type] : type
|
||||
|
||||
if (typeof Comp === 'undefined') {
|
||||
throw new Error(renderErr)
|
||||
}
|
||||
|
||||
return flexRender(Comp, {
|
||||
...instanceRef.current,
|
||||
column,
|
||||
row,
|
||||
cell,
|
||||
...userProps,
|
||||
})
|
||||
}
|
||||
|
||||
return cell
|
||||
})
|
||||
|
||||
// need to apply any row specific hooks (useExpanded requires this)
|
||||
applyHooks(instanceRef.current.hooks.prepareRow, row, instanceRef.current)
|
||||
|
||||
@@ -9,4 +9,5 @@ export { useSortBy } from './plugin-hooks/useSortBy'
|
||||
export { usePagination } from './plugin-hooks/usePagination'
|
||||
export { useRowSelect } from './plugin-hooks/useRowSelect'
|
||||
export { useRowState } from './plugin-hooks/useRowState'
|
||||
export { useColumnOrder } from './plugin-hooks/useColumnOrder'
|
||||
export { actions, addActions } from './actions'
|
||||
|
||||
@@ -19,7 +19,7 @@ Snapshot Diff:
|
||||
</pre>
|
||||
<table
|
||||
class=""
|
||||
@@ -78,10 +80,53 @@
|
||||
@@ -82,10 +84,53 @@
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -73,7 +73,7 @@ Snapshot Diff:
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
@@ -111,10 +156,139 @@
|
||||
@@ -115,10 +160,139 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -235,7 +235,7 @@ Snapshot Diff:
|
||||
</code>
|
||||
</pre>
|
||||
<table
|
||||
@@ -123,10 +125,53 @@
|
||||
@@ -127,10 +129,53 @@
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -289,7 +289,7 @@ Snapshot Diff:
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
@@ -156,10 +201,139 @@
|
||||
@@ -160,10 +205,139 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -451,7 +451,7 @@ Snapshot Diff:
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
@@ -168,10 +170,53 @@
|
||||
@@ -172,10 +174,53 @@
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -505,7 +505,7 @@ Snapshot Diff:
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
@@ -201,10 +246,139 @@
|
||||
@@ -205,10 +250,139 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -667,7 +667,7 @@ Snapshot Diff:
|
||||
}
|
||||
}
|
||||
</code>
|
||||
@@ -214,11 +216,11 @@
|
||||
@@ -218,11 +220,11 @@
|
||||
class=""
|
||||
>
|
||||
<span
|
||||
@@ -680,7 +680,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -246,10 +248,30 @@
|
||||
@@ -250,10 +252,30 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
|
||||
@@ -5,7 +5,7 @@ Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -101,11 +101,11 @@
|
||||
@@ -109,11 +109,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -18,7 +18,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -157,11 +157,11 @@
|
||||
@@ -165,11 +165,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -31,7 +31,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -213,11 +213,11 @@
|
||||
@@ -221,11 +221,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -44,7 +44,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -269,11 +269,11 @@
|
||||
@@ -277,11 +277,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -57,7 +57,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -325,11 +325,11 @@
|
||||
@@ -333,11 +333,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -70,7 +70,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -381,11 +381,11 @@
|
||||
@@ -389,11 +389,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -83,7 +83,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -437,11 +437,11 @@
|
||||
@@ -445,11 +445,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -96,7 +96,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -493,11 +493,11 @@
|
||||
@@ -501,11 +501,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -109,7 +109,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -549,11 +549,11 @@
|
||||
@@ -557,11 +557,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -122,7 +122,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -605,11 +605,11 @@
|
||||
@@ -613,11 +613,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -135,7 +135,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -661,11 +661,11 @@
|
||||
@@ -669,11 +669,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -148,7 +148,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -717,11 +717,11 @@
|
||||
@@ -725,11 +725,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -161,7 +161,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -773,11 +773,11 @@
|
||||
@@ -781,11 +781,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -174,7 +174,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -829,11 +829,11 @@
|
||||
@@ -837,11 +837,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -187,7 +187,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -885,11 +885,11 @@
|
||||
@@ -893,11 +893,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -200,7 +200,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -941,11 +941,11 @@
|
||||
@@ -949,11 +949,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -213,7 +213,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -997,11 +997,11 @@
|
||||
@@ -1005,11 +1005,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -226,7 +226,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1053,11 +1053,11 @@
|
||||
@@ -1061,11 +1061,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -239,7 +239,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1109,11 +1109,11 @@
|
||||
@@ -1117,11 +1117,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -252,7 +252,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1165,11 +1165,11 @@
|
||||
@@ -1173,11 +1173,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -265,7 +265,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1221,11 +1221,11 @@
|
||||
@@ -1229,11 +1229,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -278,7 +278,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1277,11 +1277,11 @@
|
||||
@@ -1285,11 +1285,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -291,7 +291,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1333,11 +1333,11 @@
|
||||
@@ -1341,11 +1341,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -304,7 +304,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1389,11 +1389,11 @@
|
||||
@@ -1397,11 +1397,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -317,7 +317,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1445,11 +1445,11 @@
|
||||
@@ -1453,11 +1453,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -330,7 +330,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1501,11 +1501,11 @@
|
||||
@@ -1509,11 +1509,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -343,7 +343,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1557,11 +1557,11 @@
|
||||
@@ -1565,11 +1565,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -356,7 +356,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1613,11 +1613,11 @@
|
||||
@@ -1621,11 +1621,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -369,7 +369,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1669,11 +1669,11 @@
|
||||
@@ -1677,11 +1677,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -382,7 +382,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1725,11 +1725,11 @@
|
||||
@@ -1733,11 +1733,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -395,7 +395,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1781,11 +1781,11 @@
|
||||
@@ -1789,11 +1789,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -408,7 +408,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1837,11 +1837,11 @@
|
||||
@@ -1845,11 +1845,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -421,7 +421,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1893,11 +1893,11 @@
|
||||
@@ -1901,11 +1901,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -434,7 +434,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1949,11 +1949,11 @@
|
||||
@@ -1957,11 +1957,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -447,7 +447,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -2005,11 +2005,11 @@
|
||||
@@ -2013,11 +2013,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -460,7 +460,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -2061,11 +2061,11 @@
|
||||
@@ -2069,11 +2069,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -473,7 +473,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -2098,15 +2098,52 @@
|
||||
@@ -2106,15 +2106,52 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -535,7 +535,7 @@ Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -101,11 +101,11 @@
|
||||
@@ -109,11 +109,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -548,7 +548,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -157,11 +157,11 @@
|
||||
@@ -165,11 +165,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -561,7 +561,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -213,11 +213,11 @@
|
||||
@@ -221,11 +221,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -574,7 +574,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -269,11 +269,11 @@
|
||||
@@ -277,11 +277,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -587,7 +587,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -325,11 +325,11 @@
|
||||
@@ -333,11 +333,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -600,7 +600,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -381,11 +381,11 @@
|
||||
@@ -389,11 +389,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -613,7 +613,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -437,11 +437,11 @@
|
||||
@@ -445,11 +445,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -626,7 +626,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -493,11 +493,11 @@
|
||||
@@ -501,11 +501,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -639,7 +639,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -549,11 +549,11 @@
|
||||
@@ -557,11 +557,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -652,7 +652,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -605,11 +605,11 @@
|
||||
@@ -613,11 +613,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -665,7 +665,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -661,11 +661,11 @@
|
||||
@@ -669,11 +669,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -678,7 +678,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -717,11 +717,11 @@
|
||||
@@ -725,11 +725,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -691,7 +691,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -773,11 +773,11 @@
|
||||
@@ -781,11 +781,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -704,7 +704,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -829,11 +829,11 @@
|
||||
@@ -837,11 +837,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -717,7 +717,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -885,11 +885,11 @@
|
||||
@@ -893,11 +893,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -730,7 +730,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -941,11 +941,11 @@
|
||||
@@ -949,11 +949,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -743,7 +743,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -997,11 +997,11 @@
|
||||
@@ -1005,11 +1005,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -756,7 +756,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1053,11 +1053,11 @@
|
||||
@@ -1061,11 +1061,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -769,7 +769,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1109,11 +1109,11 @@
|
||||
@@ -1117,11 +1117,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -782,7 +782,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1165,11 +1165,11 @@
|
||||
@@ -1173,11 +1173,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -795,7 +795,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1221,11 +1221,11 @@
|
||||
@@ -1229,11 +1229,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -808,7 +808,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1277,11 +1277,11 @@
|
||||
@@ -1285,11 +1285,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -821,7 +821,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1333,11 +1333,11 @@
|
||||
@@ -1341,11 +1341,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -834,7 +834,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1389,11 +1389,11 @@
|
||||
@@ -1397,11 +1397,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -847,7 +847,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1445,11 +1445,11 @@
|
||||
@@ -1453,11 +1453,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -860,7 +860,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1501,11 +1501,11 @@
|
||||
@@ -1509,11 +1509,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -873,7 +873,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1557,11 +1557,11 @@
|
||||
@@ -1565,11 +1565,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -886,7 +886,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1613,11 +1613,11 @@
|
||||
@@ -1621,11 +1621,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -899,7 +899,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1669,11 +1669,11 @@
|
||||
@@ -1677,11 +1677,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -912,7 +912,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1725,11 +1725,11 @@
|
||||
@@ -1733,11 +1733,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -925,7 +925,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1781,11 +1781,11 @@
|
||||
@@ -1789,11 +1789,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -938,7 +938,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1837,11 +1837,11 @@
|
||||
@@ -1845,11 +1845,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -951,7 +951,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1893,11 +1893,11 @@
|
||||
@@ -1901,11 +1901,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -964,7 +964,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -1949,11 +1949,11 @@
|
||||
@@ -1957,11 +1957,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -977,7 +977,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -2005,11 +2005,11 @@
|
||||
@@ -2013,11 +2013,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -990,7 +990,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -2061,11 +2061,11 @@
|
||||
@@ -2069,11 +2069,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -1003,7 +1003,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -2098,52 +2098,15 @@
|
||||
@@ -2106,52 +2106,15 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -1065,7 +1065,7 @@ Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -101,11 +101,11 @@
|
||||
@@ -109,11 +109,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -1078,7 +1078,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -213,11 +213,11 @@
|
||||
@@ -221,11 +221,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -1091,7 +1091,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -269,11 +269,11 @@
|
||||
@@ -277,11 +277,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -1104,7 +1104,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -325,11 +325,11 @@
|
||||
@@ -333,11 +333,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -1117,7 +1117,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -2098,15 +2098,20 @@
|
||||
@@ -2106,15 +2106,20 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -1147,7 +1147,7 @@ Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -213,11 +213,11 @@
|
||||
@@ -221,11 +221,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -1160,7 +1160,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -269,11 +269,11 @@
|
||||
@@ -277,11 +277,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -1173,7 +1173,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -325,11 +325,11 @@
|
||||
@@ -333,11 +333,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -1186,7 +1186,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -2098,20 +2098,17 @@
|
||||
@@ -2106,20 +2106,17 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -1216,7 +1216,7 @@ Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -269,11 +269,11 @@
|
||||
@@ -277,11 +277,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -1229,7 +1229,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -2098,17 +2098,18 @@
|
||||
@@ -2106,17 +2106,18 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -1257,7 +1257,7 @@ Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -325,11 +325,11 @@
|
||||
@@ -333,11 +333,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -1270,7 +1270,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -2098,18 +2098,19 @@
|
||||
@@ -2106,18 +2106,19 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -1299,7 +1299,7 @@ Snapshot Diff:
|
||||
- First value
|
||||
+ Second value
|
||||
|
||||
@@ -325,11 +325,11 @@
|
||||
@@ -333,11 +333,11 @@
|
||||
</td>
|
||||
<td
|
||||
class=""
|
||||
@@ -1312,7 +1312,7 @@ Snapshot Diff:
|
||||
<td
|
||||
class=""
|
||||
>
|
||||
@@ -2098,19 +2098,18 @@
|
||||
@@ -2106,19 +2106,18 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -50,7 +50,7 @@ function Table({ columns: userColumns, data, SubComponent }) {
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
columns,
|
||||
flatColumns,
|
||||
state: [{ expanded }],
|
||||
} = useTable(
|
||||
{
|
||||
@@ -90,7 +90,9 @@ function Table({ columns: userColumns, data, SubComponent }) {
|
||||
</tr>
|
||||
{!row.subRows.length && row.isExpanded ? (
|
||||
<tr>
|
||||
<td colSpan={columns.length}>{SubComponent({ row })}</td>
|
||||
<td colSpan={flatColumns.length}>
|
||||
{SubComponent({ row })}
|
||||
</td>
|
||||
</tr>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { addActions, actions } from '../actions'
|
||||
import { defaultState } from '../hooks/useTableState'
|
||||
|
||||
defaultState.columnOrder = []
|
||||
|
||||
addActions('setColumnOrder')
|
||||
|
||||
const propTypes = {
|
||||
initialRowStateAccessor: PropTypes.func,
|
||||
}
|
||||
|
||||
export const useColumnOrder = hooks => {
|
||||
hooks.columnsBeforeHeaderGroupsDeps.push((deps, instance) => {
|
||||
return [...deps, instance.state[0].columnOrder]
|
||||
})
|
||||
hooks.columnsBeforeHeaderGroups.push(columnsBeforeHeaderGroups)
|
||||
hooks.useMain.push(useMain)
|
||||
}
|
||||
|
||||
useColumnOrder.pluginName = 'useColumnOrder'
|
||||
|
||||
function columnsBeforeHeaderGroups(columns, instance) {
|
||||
const {
|
||||
state: [{ columnOrder }],
|
||||
} = instance
|
||||
|
||||
// If there is no order, return the normal columns
|
||||
if (!columnOrder || !columnOrder.length) {
|
||||
return columns
|
||||
}
|
||||
|
||||
const columnOrderCopy = [...columnOrder]
|
||||
|
||||
// If there is an order, make a copy of the columns
|
||||
const columnsCopy = [...columns]
|
||||
|
||||
// And make a new ordered array of the columns
|
||||
const columnsInOrder = []
|
||||
|
||||
// Loop over the columns and place them in order into the new array
|
||||
while (columnsCopy.length && columnOrderCopy.length) {
|
||||
const targetColumnID = columnOrderCopy.shift()
|
||||
const foundIndex = columnsCopy.findIndex(d => d.id === targetColumnID)
|
||||
if (foundIndex > -1) {
|
||||
columnsInOrder.push(columnsCopy.splice(foundIndex, 1)[0])
|
||||
}
|
||||
}
|
||||
|
||||
// If there are any columns left, add them to the end
|
||||
return [...columnsInOrder, ...columnsCopy]
|
||||
}
|
||||
|
||||
function useMain(instance) {
|
||||
PropTypes.checkPropTypes(propTypes, instance, 'property', 'useColumnOrder')
|
||||
|
||||
const {
|
||||
state: [, setState],
|
||||
} = instance
|
||||
|
||||
const setColumnOrder = React.useCallback(
|
||||
updater => {
|
||||
return setState(old => {
|
||||
return {
|
||||
...old,
|
||||
columnOrder:
|
||||
typeof updater === 'function' ? updater(old.columnOrder) : updater,
|
||||
}
|
||||
}, actions.setColumnOrder)
|
||||
},
|
||||
[setState]
|
||||
)
|
||||
|
||||
return {
|
||||
...instance,
|
||||
setColumnOrder,
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ function useMain(instance) {
|
||||
const {
|
||||
debug,
|
||||
rows,
|
||||
columns,
|
||||
flatColumns,
|
||||
filterTypes: userFilterTypes,
|
||||
manualFilters,
|
||||
disableFilters,
|
||||
@@ -43,7 +43,7 @@ function useMain(instance) {
|
||||
const preFilteredRows = rows
|
||||
|
||||
const setFilter = (id, updater) => {
|
||||
const column = columns.find(d => d.id === id)
|
||||
const column = flatColumns.find(d => d.id === id)
|
||||
|
||||
if (!column) {
|
||||
throw new Error(`React-Table: Could not find a column with id: ${id}`)
|
||||
@@ -85,7 +85,7 @@ function useMain(instance) {
|
||||
// Filter out undefined values
|
||||
Object.keys(newFilters).forEach(id => {
|
||||
const newFilter = newFilters[id]
|
||||
const column = columns.find(d => d.id === id)
|
||||
const column = flatColumns.find(d => d.id === id)
|
||||
const filterMethod = getFilterMethod(
|
||||
column.filter,
|
||||
userFilterTypes || {},
|
||||
@@ -104,7 +104,7 @@ function useMain(instance) {
|
||||
}, actions.setAllFilters)
|
||||
}
|
||||
|
||||
columns.forEach(column => {
|
||||
flatColumns.forEach(column => {
|
||||
const { id, accessor, disableFilters: columnDisableFilters } = column
|
||||
|
||||
// Determine if a column is filterable
|
||||
@@ -144,7 +144,7 @@ function useMain(instance) {
|
||||
filteredRows = Object.entries(filters).reduce(
|
||||
(filteredSoFar, [columnID, filterValue]) => {
|
||||
// Find the filters column
|
||||
const column = columns.find(d => d.id === columnID)
|
||||
const column = flatColumns.find(d => d.id === columnID)
|
||||
|
||||
if (!column) {
|
||||
return filteredSoFar
|
||||
@@ -195,12 +195,12 @@ function useMain(instance) {
|
||||
}
|
||||
|
||||
return filterRows(rows)
|
||||
}, [manualFilters, filters, debug, rows, columns, userFilterTypes])
|
||||
}, [manualFilters, filters, debug, rows, flatColumns, userFilterTypes])
|
||||
|
||||
React.useMemo(() => {
|
||||
// Now that each filtered column has it's partially filtered rows,
|
||||
// lets assign the final filtered rows to all of the other columns
|
||||
const nonFilteredColumns = columns.filter(
|
||||
const nonFilteredColumns = flatColumns.filter(
|
||||
column => !Object.keys(filters).includes(column.id)
|
||||
)
|
||||
|
||||
@@ -209,7 +209,7 @@ function useMain(instance) {
|
||||
nonFilteredColumns.forEach(column => {
|
||||
column.preFilteredRows = filteredRows
|
||||
})
|
||||
}, [columns, filteredRows, filters])
|
||||
}, [filteredRows, filters, flatColumns])
|
||||
|
||||
return {
|
||||
...instance,
|
||||
|
||||
@@ -48,16 +48,16 @@ export const useGroupBy = hooks => {
|
||||
|
||||
useGroupBy.pluginName = 'useGroupBy'
|
||||
|
||||
function columnsBeforeHeaderGroups(columns, { state: [{ groupBy }] }) {
|
||||
function columnsBeforeHeaderGroups(flatColumns, { state: [{ groupBy }] }) {
|
||||
// Sort grouped columns to the start of the column list
|
||||
// before the headers are built
|
||||
|
||||
const groupByColumns = groupBy.map(g => columns.find(col => col.id === g))
|
||||
const nonGroupByColumns = columns.filter(col => !groupBy.includes(col.id))
|
||||
const groupByColumns = groupBy.map(g => flatColumns.find(col => col.id === g))
|
||||
const nonGroupByColumns = flatColumns.filter(col => !groupBy.includes(col.id))
|
||||
|
||||
// If a groupByBoundary column is found, place the groupBy's after it
|
||||
const groupByBoundaryColumnIndex =
|
||||
columns.findIndex(column => column.groupByBoundary) + 1
|
||||
flatColumns.findIndex(column => column.groupByBoundary) + 1
|
||||
|
||||
return [
|
||||
...nonGroupByColumns.slice(0, groupByBoundaryColumnIndex),
|
||||
@@ -72,8 +72,8 @@ function useMain(instance) {
|
||||
const {
|
||||
debug,
|
||||
rows,
|
||||
columns,
|
||||
headers,
|
||||
flatColumns,
|
||||
flatHeaders,
|
||||
groupByFn = defaultGroupByFn,
|
||||
manualGroupBy,
|
||||
disableGrouping,
|
||||
@@ -85,7 +85,7 @@ function useMain(instance) {
|
||||
|
||||
ensurePluginOrder(plugins, [], 'useGroupBy', ['useExpanded'])
|
||||
|
||||
columns.forEach(column => {
|
||||
flatColumns.forEach(column => {
|
||||
const { id, accessor, disableGrouping: columnDisableGrouping } = column
|
||||
column.isGrouped = groupBy.includes(id)
|
||||
column.groupedIndex = groupBy.indexOf(id)
|
||||
@@ -124,16 +124,15 @@ function useMain(instance) {
|
||||
|
||||
hooks.getGroupByToggleProps = []
|
||||
|
||||
//
|
||||
;[...columns, ...headers].forEach(column => {
|
||||
const { canGroupBy } = column
|
||||
column.getGroupByToggleProps = props => {
|
||||
flatHeaders.forEach(header => {
|
||||
const { canGroupBy } = header
|
||||
header.getGroupByToggleProps = props => {
|
||||
return mergeProps(
|
||||
{
|
||||
onClick: canGroupBy
|
||||
? e => {
|
||||
e.persist()
|
||||
column.toggleGroupBy()
|
||||
header.toggleGroupBy()
|
||||
}
|
||||
: undefined,
|
||||
style: {
|
||||
@@ -141,7 +140,7 @@ function useMain(instance) {
|
||||
},
|
||||
title: 'Toggle GroupBy',
|
||||
},
|
||||
applyPropHooks(instance.hooks.getGroupByToggleProps, column, instance),
|
||||
applyPropHooks(instance.hooks.getGroupByToggleProps, header, instance),
|
||||
props
|
||||
)
|
||||
}
|
||||
@@ -173,7 +172,7 @@ function useMain(instance) {
|
||||
const aggregateRowsToValues = (rows, isSourceRows) => {
|
||||
const values = {}
|
||||
|
||||
columns.forEach(column => {
|
||||
flatColumns.forEach(column => {
|
||||
// Don't aggregate columns that are in the groupBy
|
||||
if (groupBy.includes(column.id)) {
|
||||
values[column.id] = rows[0] ? rows[0].values[column.id] : null
|
||||
@@ -266,7 +265,7 @@ function useMain(instance) {
|
||||
groupBy,
|
||||
debug,
|
||||
rows,
|
||||
columns,
|
||||
flatColumns,
|
||||
userAggregations,
|
||||
groupByFn,
|
||||
])
|
||||
|
||||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
|
||||
//
|
||||
import { addActions, actions } from '../actions'
|
||||
import { defaultState } from '../hooks/useTableState'
|
||||
import { ensurePluginOrder } from '../utils'
|
||||
import { ensurePluginOrder, safeUseLayoutEffect } from '../utils'
|
||||
|
||||
defaultState.pageSize = 10
|
||||
defaultState.pageIndex = 0
|
||||
@@ -17,12 +17,6 @@ const propTypes = {
|
||||
paginateExpandedRows: PropTypes.bool,
|
||||
}
|
||||
|
||||
// SSR has issues with useLayoutEffect still, so use useEffect during SSR
|
||||
let useLayoutEffect =
|
||||
typeof window !== 'undefined' && process.env.NODE_ENV === 'production'
|
||||
? React.useLayoutEffect
|
||||
: React.useEffect
|
||||
|
||||
export const usePagination = hooks => {
|
||||
hooks.useMain.push(useMain)
|
||||
}
|
||||
@@ -55,7 +49,7 @@ function useMain(instance) {
|
||||
|
||||
const isPageIndexMountedRef = React.useRef()
|
||||
|
||||
useLayoutEffect(() => {
|
||||
safeUseLayoutEffect(() => {
|
||||
if (isPageIndexMountedRef.current && !disablePageResetOnDataChange) {
|
||||
setState(
|
||||
old => ({
|
||||
|
||||
@@ -49,7 +49,7 @@ function useMain(instance) {
|
||||
const {
|
||||
debug,
|
||||
rows,
|
||||
columns,
|
||||
flatColumns,
|
||||
orderByFn = defaultOrderByFn,
|
||||
sortTypes: userSortTypes,
|
||||
manualSorting,
|
||||
@@ -75,7 +75,7 @@ function useMain(instance) {
|
||||
const { sortBy } = old
|
||||
|
||||
// Find the column for this columnID
|
||||
const column = columns.find(d => d.id === columnID)
|
||||
const column = flatColumns.find(d => d.id === columnID)
|
||||
const { sortDescFirst } = column
|
||||
|
||||
// Find any existing sortBy for this column
|
||||
@@ -213,7 +213,7 @@ function useMain(instance) {
|
||||
|
||||
// Filter out sortBys that correspond to non existing columns
|
||||
const availableSortBy = sortBy.filter(sort =>
|
||||
columns.find(col => col.id === sort.id)
|
||||
flatColumns.find(col => col.id === sort.id)
|
||||
)
|
||||
|
||||
const sortData = rows => {
|
||||
@@ -224,7 +224,7 @@ function useMain(instance) {
|
||||
rows,
|
||||
availableSortBy.map(sort => {
|
||||
// Support custom sorting methods for each column
|
||||
const column = columns.find(d => d.id === sort.id)
|
||||
const column = flatColumns.find(d => d.id === sort.id)
|
||||
|
||||
if (!column) {
|
||||
throw new Error(
|
||||
@@ -254,7 +254,7 @@ function useMain(instance) {
|
||||
// Map the directions
|
||||
availableSortBy.map(sort => {
|
||||
// Detect and use the sortInverted option
|
||||
const column = columns.find(d => d.id === sort.id)
|
||||
const column = flatColumns.find(d => d.id === sort.id)
|
||||
|
||||
if (column && column.sortInverted) {
|
||||
return sort.desc
|
||||
@@ -279,7 +279,15 @@ function useMain(instance) {
|
||||
console.timeEnd('getSortedRows')
|
||||
|
||||
return sortData(rows)
|
||||
}, [manualSorting, sortBy, debug, columns, rows, orderByFn, userSortTypes])
|
||||
}, [
|
||||
manualSorting,
|
||||
sortBy,
|
||||
debug,
|
||||
rows,
|
||||
flatColumns,
|
||||
orderByFn,
|
||||
userSortTypes,
|
||||
])
|
||||
|
||||
return {
|
||||
...instance,
|
||||
|
||||
+42
-21
@@ -1,5 +1,17 @@
|
||||
import React from 'react'
|
||||
|
||||
const columnFallbacks = {
|
||||
Header: () => null,
|
||||
Cell: ({ cell: { value = '' } }) => value,
|
||||
show: true,
|
||||
}
|
||||
|
||||
// SSR has issues with useLayoutEffect still, so use useEffect during SSR
|
||||
export const safeUseLayoutEffect =
|
||||
typeof window !== 'undefined' && process.env.NODE_ENV === 'production'
|
||||
? React.useLayoutEffect
|
||||
: React.useEffect
|
||||
|
||||
// Find the depth of the columns
|
||||
export function findMaxDepth(columns, depth = 0) {
|
||||
return columns.reduce((prev, curr) => {
|
||||
@@ -38,9 +50,7 @@ export function decorateColumn(column, defaultColumn, parent, depth, index) {
|
||||
}
|
||||
|
||||
column = {
|
||||
Header: () => null,
|
||||
Cell: ({ cell: { value = '' } }) => value,
|
||||
show: true,
|
||||
...columnFallbacks,
|
||||
...column,
|
||||
id,
|
||||
accessor,
|
||||
@@ -69,42 +79,53 @@ export function decorateColumnTree(columns, defaultColumn, parent, depth = 0) {
|
||||
}
|
||||
|
||||
// Build the header groups from the bottom up
|
||||
export function makeHeaderGroups(columns, maxDepth, defaultColumn) {
|
||||
export function makeHeaderGroups(flatColumns, columns, defaultColumn) {
|
||||
const headerGroups = []
|
||||
|
||||
const buildGroup = (columns, depth = 0) => {
|
||||
const maxDepth = findMaxDepth(columns)
|
||||
|
||||
// Build each header group from the bottom up
|
||||
const buildGroup = (columns, depth) => {
|
||||
const headerGroup = {
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const parentColumns = []
|
||||
|
||||
// Do any of these columns have parents?
|
||||
const hasParents = columns.some(col => col.parent)
|
||||
|
||||
columns.forEach(column => {
|
||||
// Are we the first column in this group?
|
||||
const isFirst = !parentColumns.length
|
||||
|
||||
// What is the latest (last) parent column?
|
||||
let latestParentColumn = [...parentColumns].reverse()[0]
|
||||
|
||||
// If the column has a parent, add it if necessary
|
||||
if (column.parent) {
|
||||
const similarParentColumns = parentColumns.filter(
|
||||
d => d.originalID === column.parent.id
|
||||
)
|
||||
if (isFirst || latestParentColumn.originalID !== column.parent.id) {
|
||||
parentColumns.push({
|
||||
...column.parent,
|
||||
originalID: column.parent.id,
|
||||
id: [column.parent.id, parentColumns.length].join('_'),
|
||||
id: [column.parent.id, similarParentColumns.length].join('_'),
|
||||
})
|
||||
}
|
||||
} else if (hasParents) {
|
||||
// If other columns have parents, add a place holder if necessary
|
||||
// If other columns have parents, we'll need to add a place holder if necessary
|
||||
const originalID = [column.id, 'placeholder'].join('_')
|
||||
const similarParentColumns = parentColumns.filter(
|
||||
d => d.originalID === originalID
|
||||
)
|
||||
const placeholderColumn = decorateColumn(
|
||||
{
|
||||
originalID: [column.id, 'placeholder', maxDepth - depth].join('_'),
|
||||
id: [
|
||||
column.id,
|
||||
'placeholder',
|
||||
maxDepth - depth,
|
||||
parentColumns.length,
|
||||
].join('_'),
|
||||
originalID,
|
||||
id: [column.id, 'placeholder', similarParentColumns.length].join(
|
||||
'_'
|
||||
),
|
||||
},
|
||||
defaultColumn
|
||||
)
|
||||
@@ -131,23 +152,22 @@ export function makeHeaderGroups(columns, maxDepth, defaultColumn) {
|
||||
0
|
||||
)
|
||||
: 1 // Leaf node columns take up at least one count
|
||||
|
||||
headerGroup.headers.push(column)
|
||||
})
|
||||
|
||||
headerGroups.push(headerGroup)
|
||||
|
||||
if (parentColumns.length) {
|
||||
buildGroup(parentColumns)
|
||||
buildGroup(parentColumns, depth + 1)
|
||||
}
|
||||
}
|
||||
|
||||
buildGroup(columns)
|
||||
buildGroup(flatColumns, 0)
|
||||
|
||||
return headerGroups.reverse()
|
||||
}
|
||||
|
||||
export function determineColumnVisibility(instance) {
|
||||
export function determineHeaderVisibility(instance) {
|
||||
const { headers } = instance
|
||||
|
||||
const handleColumn = (column, parentVisible) => {
|
||||
@@ -156,14 +176,15 @@ export function determineColumnVisibility(instance) {
|
||||
? column.show(instance)
|
||||
: !!column.show
|
||||
: false
|
||||
if (column.columns && column.columns.length) {
|
||||
column.columns.forEach(subColumn =>
|
||||
|
||||
if (column.headers && column.headers.length) {
|
||||
column.headers.forEach(subColumn =>
|
||||
handleColumn(subColumn, column.isVisible)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
headers.forEach(subColumn => handleColumn(subColumn, true))
|
||||
headers.forEach(subHeader => handleColumn(subHeader, true))
|
||||
}
|
||||
|
||||
export function getBy(obj, path, def) {
|
||||
|
||||
Reference in New Issue
Block a user