mirror of
https://github.com/gosticks/react-table.git
synced 2025-10-16 11:55:36 +00:00
docs(examples/material-ui-enhanced-table): Add more Material UI table (#1847)
* docs(examples/material-ui-enhanced-table): add more Material UI table This enhanced Material UI table demonstrates client side pagination, sorting, global search, add row, and delete row. * Update EnhancedTable.js Co-authored-by: Tanner Linsley <tannerlinsley@gmail.com>
This commit is contained in:
parent
164b3d6b7e
commit
2bf99aaea0
@ -1,21 +1,13 @@
|
||||
{
|
||||
"dist/index.js": {
|
||||
"bundled": 113198,
|
||||
"minified": 52513,
|
||||
"gzipped": 13835
|
||||
"bundled": 113244,
|
||||
"minified": 52531,
|
||||
"gzipped": 13840
|
||||
},
|
||||
"dist/index.es.js": {
|
||||
"bundled": 112261,
|
||||
"minified": 51677,
|
||||
"gzipped": 13669,
|
||||
"bundled": 126701,
|
||||
"minified": 59700,
|
||||
"gzipped": 15373
|
||||
},
|
||||
"dist/index.es.js": {
|
||||
"bundled": 125788,
|
||||
"minified": 58888,
|
||||
"gzipped": 15205,
|
||||
"bundled": 112307,
|
||||
"minified": 51695,
|
||||
"gzipped": 13674,
|
||||
"treeshaked": {
|
||||
"rollup": {
|
||||
"code": 80,
|
||||
|
||||
@ -73,6 +73,9 @@
|
||||
- Material-UI
|
||||
- [Source](https://github.com/tannerlinsley/react-table/tree/master/examples/material-UI-components)
|
||||
- [Open in CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/material-UI-components)
|
||||
- Material-UI Enhanced Table
|
||||
- [Source](https://github.com/tannerlinsley/react-table/tree/master/examples/material-UI-enhanced-table)
|
||||
- [Open in CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/material-UI-enhanced-table)
|
||||
- [ ] Styled-Components
|
||||
- [ ] CSS
|
||||
- [ ] Bootstrap
|
||||
|
||||
3
examples/material-UI-enhanced-table/.babelrc
Normal file
3
examples/material-UI-enhanced-table/.babelrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["react-app"]
|
||||
}
|
||||
1
examples/material-UI-enhanced-table/.env
Normal file
1
examples/material-UI-enhanced-table/.env
Normal file
@ -0,0 +1 @@
|
||||
SKIP_PREFLIGHT_CHECK=true
|
||||
7
examples/material-UI-enhanced-table/.eslintrc
Normal file
7
examples/material-UI-enhanced-table/.eslintrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": ["react-app", "prettier"],
|
||||
"rules": {
|
||||
// "eqeqeq": 0,
|
||||
// "jsx-a11y/anchor-is-valid": 0
|
||||
}
|
||||
}
|
||||
23
examples/material-UI-enhanced-table/.gitignore
vendored
Normal file
23
examples/material-UI-enhanced-table/.gitignore
vendored
Normal file
@ -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*
|
||||
29
examples/material-UI-enhanced-table/.rescriptsrc.js
Normal file
29
examples/material-UI-enhanced-table/.rescriptsrc.js
Normal file
@ -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,
|
||||
]
|
||||
8
examples/material-UI-enhanced-table/README.md
Normal file
8
examples/material-UI-enhanced-table/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
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/material-UI-enhanced-table)
|
||||
- `yarn` and `yarn start` to run and edit the example
|
||||
|
||||
It demonstrates client side pagination, sorting, global search, add row, and delete row.
|
||||
37
examples/material-UI-enhanced-table/package.json
Normal file
37
examples/material-UI-enhanced-table/package.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "rescripts start",
|
||||
"build": "rescripts build",
|
||||
"test": "rescripts test",
|
||||
"eject": "rescripts eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.8.3",
|
||||
"@material-ui/icons": "^4.5.1",
|
||||
"namor": "^1.1.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-scripts": "3.0.1",
|
||||
"react-table": "latest"
|
||||
},
|
||||
"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",
|
||||
"eslint-config-prettier": "^6.3.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
examples/material-UI-enhanced-table/public/favicon.ico
Normal file
BIN
examples/material-UI-enhanced-table/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
38
examples/material-UI-enhanced-table/public/index.html
Normal file
38
examples/material-UI-enhanced-table/public/index.html
Normal file
@ -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>
|
||||
15
examples/material-UI-enhanced-table/public/manifest.json
Normal file
15
examples/material-UI-enhanced-table/public/manifest.json
Normal file
@ -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"
|
||||
}
|
||||
77
examples/material-UI-enhanced-table/src/App.js
Normal file
77
examples/material-UI-enhanced-table/src/App.js
Normal file
@ -0,0 +1,77 @@
|
||||
import React from 'react'
|
||||
|
||||
import CssBaseline from '@material-ui/core/CssBaseline'
|
||||
import EnhancedTable from './components/EnhancedTable'
|
||||
import makeData from './makeData'
|
||||
|
||||
const App = () => {
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
},
|
||||
{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
},
|
||||
{
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
},
|
||||
{
|
||||
Header: 'Profile Progress',
|
||||
accessor: 'progress',
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
const [data, setData] = React.useState(React.useMemo(() => makeData(20), []))
|
||||
const [skipPageReset, setSkipPageReset] = React.useState(false)
|
||||
|
||||
// We need to keep the table from resetting the pageIndex when we
|
||||
// Update data. So we can keep track of that flag with a ref.
|
||||
|
||||
// When our cell renderer calls updateMyData, we'll use
|
||||
// the rowIndex, columnId and new value to update the
|
||||
// original data
|
||||
const updateMyData = (rowIndex, columnId, value) => {
|
||||
// We also turn on the flag to not reset the page
|
||||
setSkipPageReset(true)
|
||||
setData(old =>
|
||||
old.map((row, index) => {
|
||||
if (index === rowIndex) {
|
||||
return {
|
||||
...old[rowIndex],
|
||||
[columnId]: value,
|
||||
}
|
||||
}
|
||||
return row
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CssBaseline />
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={data}
|
||||
setData={setData}
|
||||
updateMyData={updateMyData}
|
||||
skipPageReset={skipPageReset}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
9
examples/material-UI-enhanced-table/src/App.test.js
Normal file
9
examples/material-UI-enhanced-table/src/App.test.js
Normal file
@ -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,152 @@
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import AddIcon from '@material-ui/icons/Add'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import PropTypes from 'prop-types'
|
||||
import Switch from '@material-ui/core/Switch'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import Tooltip from '@material-ui/core/Tooltip'
|
||||
|
||||
const initialUser = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
age: 0,
|
||||
visits: 0,
|
||||
status: 'single',
|
||||
progress: 0,
|
||||
subRows: undefined,
|
||||
}
|
||||
|
||||
const AddUserDialog = props => {
|
||||
const [user, setUser] = useState(initialUser)
|
||||
const { addUserHandler } = props
|
||||
const [open, setOpen] = React.useState(false)
|
||||
|
||||
const [switchState, setSwitchState] = React.useState({
|
||||
addMultiple: false,
|
||||
})
|
||||
|
||||
const handleSwitchChange = name => event => {
|
||||
setSwitchState({ ...switchState, [name]: event.target.checked })
|
||||
}
|
||||
|
||||
const resetSwitch = () => {
|
||||
setSwitchState({ addMultiple: false })
|
||||
}
|
||||
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false)
|
||||
resetSwitch()
|
||||
}
|
||||
|
||||
const handleAdd = event => {
|
||||
addUserHandler(user)
|
||||
setUser(initialUser)
|
||||
switchState.addMultiple ? setOpen(true) : setOpen(false)
|
||||
}
|
||||
|
||||
const handleChange = name => ({ target: { value } }) => {
|
||||
setUser({ ...user, [name]: value })
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Tooltip title="Add">
|
||||
<IconButton aria-label="add" onClick={handleClickOpen}>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
aria-labelledby="form-dialog-title"
|
||||
>
|
||||
<DialogTitle id="form-dialog-title">Add User</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>Demo add item to react table.</DialogContentText>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
label="First Name"
|
||||
type="text"
|
||||
fullWidth
|
||||
value={user.firstName}
|
||||
onChange={handleChange('firstName')}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Last Name"
|
||||
type="text"
|
||||
fullWidth
|
||||
value={user.lastName}
|
||||
onChange={handleChange('lastName')}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Age"
|
||||
type="number"
|
||||
fullWidth
|
||||
value={user.age}
|
||||
onChange={handleChange('age')}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Visits"
|
||||
type="number"
|
||||
fullWidth
|
||||
value={user.visits}
|
||||
onChange={handleChange('visits')}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Status"
|
||||
type="text"
|
||||
fullWidth
|
||||
value={user.status}
|
||||
onChange={handleChange('status')}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Profile Progress"
|
||||
type="number"
|
||||
fullWidth
|
||||
value={user.progress}
|
||||
onChange={handleChange('progress')}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Tooltip title="Add multiple">
|
||||
<Switch
|
||||
checked={switchState.addMultiple}
|
||||
onChange={handleSwitchChange('addMultiple')}
|
||||
value="addMultiple"
|
||||
inputProps={{ 'aria-label': 'secondary checkbox' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Button onClick={handleClose} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleAdd} color="primary">
|
||||
Add
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
AddUserDialog.propTypes = {
|
||||
addUserHandler: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default AddUserDialog
|
||||
@ -0,0 +1,274 @@
|
||||
import React from 'react'
|
||||
|
||||
import Checkbox from '@material-ui/core/Checkbox'
|
||||
import MaUTable from '@material-ui/core/Table'
|
||||
import PropTypes from 'prop-types'
|
||||
import TableBody from '@material-ui/core/TableBody'
|
||||
import TableCell from '@material-ui/core/TableCell'
|
||||
import TableContainer from '@material-ui/core/TableContainer'
|
||||
import TableFooter from '@material-ui/core/TableFooter'
|
||||
import TableHead from '@material-ui/core/TableHead'
|
||||
import TablePagination from '@material-ui/core/TablePagination'
|
||||
import TablePaginationActions from './TablePaginationActions'
|
||||
import TableRow from '@material-ui/core/TableRow'
|
||||
import TableSortLabel from '@material-ui/core/TableSortLabel'
|
||||
import TableToolbar from './TableToolbar'
|
||||
import {
|
||||
useGlobalFilter,
|
||||
usePagination,
|
||||
useRowSelect,
|
||||
useSortBy,
|
||||
useTable,
|
||||
} from 'react-table'
|
||||
|
||||
const IndeterminateCheckbox = React.forwardRef(
|
||||
({ indeterminate, ...rest }, ref) => {
|
||||
const defaultRef = React.useRef()
|
||||
const resolvedRef = ref || defaultRef
|
||||
|
||||
React.useEffect(() => {
|
||||
resolvedRef.current.indeterminate = indeterminate
|
||||
}, [resolvedRef, indeterminate])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Checkbox ref={resolvedRef} {...rest} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const inputStyle = {
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
border: 0,
|
||||
background: 'transparent',
|
||||
}
|
||||
|
||||
// Create an editable cell renderer
|
||||
const EditableCell = ({
|
||||
cell: { value: initialValue },
|
||||
row: { index },
|
||||
column: { id },
|
||||
updateMyData, // This is a custom function that we supplied to our table instance
|
||||
}) => {
|
||||
// We need to keep and update the state of the cell normally
|
||||
const [value, setValue] = React.useState(initialValue)
|
||||
|
||||
const onChange = e => {
|
||||
setValue(e.target.value)
|
||||
}
|
||||
|
||||
// We'll only update the external data when the input is blurred
|
||||
const onBlur = () => {
|
||||
updateMyData(index, id, value)
|
||||
}
|
||||
|
||||
// If the initialValue is changed externall, sync it up with our state
|
||||
React.useEffect(() => {
|
||||
setValue(initialValue)
|
||||
}, [initialValue])
|
||||
|
||||
return (
|
||||
<input
|
||||
style={inputStyle}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
EditableCell.propTypes = {
|
||||
cell: PropTypes.shape({
|
||||
value: PropTypes.any.isRequired,
|
||||
}),
|
||||
row: PropTypes.shape({
|
||||
index: PropTypes.number.isRequired,
|
||||
}),
|
||||
column: PropTypes.shape({
|
||||
id: PropTypes.number.isRequired,
|
||||
}),
|
||||
updateMyData: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
// Set our editable cell renderer as the default Cell renderer
|
||||
const defaultColumn = {
|
||||
Cell: EditableCell,
|
||||
}
|
||||
|
||||
const EnhancedTable = ({
|
||||
columns,
|
||||
data,
|
||||
setData,
|
||||
updateMyData,
|
||||
skipPageReset,
|
||||
}) => {
|
||||
const {
|
||||
getTableProps,
|
||||
headerGroups,
|
||||
prepareRow,
|
||||
page,
|
||||
gotoPage,
|
||||
setPageSize,
|
||||
preGlobalFilteredRows,
|
||||
setGlobalFilter,
|
||||
state: { pageIndex, pageSize, selectedRowIds, globalFilter },
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
defaultColumn,
|
||||
autoResetPage: !skipPageReset,
|
||||
// updateMyData isn't part of the API, but
|
||||
// anything we put into these options will
|
||||
// automatically be available on the instance.
|
||||
// That way we can call this function from our
|
||||
// cell renderer!
|
||||
updateMyData,
|
||||
},
|
||||
useGlobalFilter,
|
||||
useSortBy,
|
||||
usePagination,
|
||||
useRowSelect,
|
||||
hooks => {
|
||||
hooks.allColumns.push(columns => [
|
||||
// Let's make a column for selection
|
||||
{
|
||||
id: 'selection',
|
||||
// The header can use the table's getToggleAllRowsSelectedProps method
|
||||
// to render a checkbox. Pagination is a problem since this will select all
|
||||
// rows even though not all rows are on the current page. The solution should
|
||||
// be server side pagination. For one, the clients should not download all
|
||||
// rows in most cases. The client should only download data for the current page.
|
||||
// In that case, getToggleAllRowsSelectedProps works fine.
|
||||
Header: ({ getToggleAllRowsSelectedProps }) => (
|
||||
<div>
|
||||
<IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
|
||||
</div>
|
||||
),
|
||||
// The cell can use the individual row's getToggleRowSelectedProps method
|
||||
// to the render a checkbox
|
||||
Cell: ({ row }) => (
|
||||
<div>
|
||||
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
...columns,
|
||||
])
|
||||
}
|
||||
)
|
||||
|
||||
const handleChangePage = (event, newPage) => {
|
||||
gotoPage(newPage)
|
||||
}
|
||||
|
||||
const handleChangeRowsPerPage = event => {
|
||||
setPageSize(Number(event.target.value))
|
||||
}
|
||||
|
||||
const removeByIndexs = (array, indexs) =>
|
||||
array.filter((_, i) => !indexs.includes(i))
|
||||
|
||||
const deleteUserHandler = event => {
|
||||
const newData = removeByIndexs(
|
||||
data,
|
||||
Object.keys(selectedRowIds).map(x => parseInt(x, 10))
|
||||
)
|
||||
setData(newData)
|
||||
}
|
||||
|
||||
const addUserHandler = user => {
|
||||
const newData = data.concat([user])
|
||||
setData(newData)
|
||||
}
|
||||
|
||||
// Render the UI for your table
|
||||
return (
|
||||
<TableContainer>
|
||||
<TableToolbar
|
||||
numSelected={Object.keys(selectedRowIds).length}
|
||||
deleteUserHandler={deleteUserHandler}
|
||||
addUserHandler={addUserHandler}
|
||||
preGlobalFilteredRows={preGlobalFilteredRows}
|
||||
setGlobalFilter={setGlobalFilter}
|
||||
globalFilter={globalFilter}
|
||||
/>
|
||||
<MaUTable {...getTableProps()}>
|
||||
<TableHead>
|
||||
{headerGroups.map(headerGroup => (
|
||||
<TableRow {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map(column => (
|
||||
<TableCell
|
||||
{...(column.id === 'selection'
|
||||
? column.getHeaderProps()
|
||||
: column.getHeaderProps(column.getSortByToggleProps()))}
|
||||
>
|
||||
{column.render('Header')}
|
||||
{column.id !== 'selection' ? (
|
||||
<TableSortLabel
|
||||
active={column.isSorted}
|
||||
// react-table has a unsorted state which is not treated here
|
||||
direction={column.isSortedDesc ? 'desc' : 'asc'}
|
||||
/>
|
||||
) : null}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{page.map((row, i) => {
|
||||
prepareRow(row)
|
||||
return (
|
||||
<TableRow {...row.getRowProps()}>
|
||||
{row.cells.map(cell => {
|
||||
return (
|
||||
<TableCell {...cell.getCellProps()}>
|
||||
{cell.render('Cell')}
|
||||
</TableCell>
|
||||
)
|
||||
})}
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
</TableBody>
|
||||
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[
|
||||
5,
|
||||
10,
|
||||
25,
|
||||
{ label: 'All', value: data.length },
|
||||
]}
|
||||
colSpan={3}
|
||||
count={data.length}
|
||||
rowsPerPage={pageSize}
|
||||
page={pageIndex}
|
||||
SelectProps={{
|
||||
inputProps: { 'aria-label': 'rows per page' },
|
||||
native: true,
|
||||
}}
|
||||
onChangePage={handleChangePage}
|
||||
onChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
ActionsComponent={TablePaginationActions}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</MaUTable>
|
||||
</TableContainer>
|
||||
)
|
||||
}
|
||||
|
||||
EnhancedTable.propTypes = {
|
||||
columns: PropTypes.array.isRequired,
|
||||
data: PropTypes.array.isRequired,
|
||||
updateMyData: PropTypes.func.isRequired,
|
||||
setData: PropTypes.func.isRequired,
|
||||
skipPageReset: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
export default EnhancedTable
|
||||
@ -0,0 +1,85 @@
|
||||
import React from 'react'
|
||||
|
||||
import InputBase from '@material-ui/core/InputBase'
|
||||
import { fade, makeStyles } from '@material-ui/core/styles'
|
||||
import PropTypes from 'prop-types'
|
||||
import SearchIcon from '@material-ui/icons/Search'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
search: {
|
||||
position: 'relative',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
backgroundColor: fade(theme.palette.common.white, 0.15),
|
||||
'&:hover': {
|
||||
backgroundColor: fade(theme.palette.common.white, 0.25),
|
||||
},
|
||||
marginRight: theme.spacing(2),
|
||||
marginLeft: 0,
|
||||
width: '100%',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
marginLeft: theme.spacing(3),
|
||||
width: 'auto',
|
||||
},
|
||||
},
|
||||
searchIcon: {
|
||||
width: theme.spacing(7),
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
pointerEvents: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
inputRoot: {
|
||||
color: 'inherit',
|
||||
},
|
||||
inputInput: {
|
||||
padding: theme.spacing(1, 1, 1, 7),
|
||||
transition: theme.transitions.create('width'),
|
||||
width: '100%',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
width: 200,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const GlobalFilter = ({
|
||||
preGlobalFilteredRows,
|
||||
globalFilter,
|
||||
setGlobalFilter,
|
||||
}) => {
|
||||
const classes = useStyles()
|
||||
const count = preGlobalFilteredRows.length
|
||||
|
||||
// Global filter only works with pagination from the first page.
|
||||
// This may not be a problem for server side pagination when
|
||||
// only the current page is downloaded.
|
||||
|
||||
return (
|
||||
<div className={classes.search}>
|
||||
<div className={classes.searchIcon}>
|
||||
<SearchIcon />
|
||||
</div>
|
||||
<InputBase
|
||||
value={globalFilter || ''}
|
||||
onChange={e => {
|
||||
setGlobalFilter(e.target.value || undefined) // Set undefined to remove the filter entirely
|
||||
}}
|
||||
placeholder={`${count} records...`}
|
||||
classes={{
|
||||
root: classes.inputRoot,
|
||||
input: classes.inputInput,
|
||||
}}
|
||||
inputProps={{ 'aria-label': 'search' }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
GlobalFilter.propTypes = {
|
||||
preGlobalFilteredRows: PropTypes.array.isRequired,
|
||||
globalFilter: PropTypes.string.isRequired,
|
||||
setGlobalFilter: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default GlobalFilter
|
||||
@ -0,0 +1,88 @@
|
||||
import React from 'react'
|
||||
|
||||
import FirstPageIcon from '@material-ui/icons/FirstPage'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft'
|
||||
import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight'
|
||||
import LastPageIcon from '@material-ui/icons/LastPage'
|
||||
import { makeStyles, useTheme } from '@material-ui/core/styles'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
flexShrink: 0,
|
||||
marginLeft: theme.spacing(2.5),
|
||||
},
|
||||
}))
|
||||
|
||||
const TablePaginationActions = props => {
|
||||
const classes = useStyles()
|
||||
const theme = useTheme()
|
||||
const { count, page, rowsPerPage, onChangePage } = props
|
||||
|
||||
const handleFirstPageButtonClick = event => {
|
||||
onChangePage(event, 0)
|
||||
}
|
||||
|
||||
const handleBackButtonClick = event => {
|
||||
onChangePage(event, page - 1)
|
||||
}
|
||||
|
||||
const handleNextButtonClick = event => {
|
||||
onChangePage(event, page + 1)
|
||||
}
|
||||
|
||||
const handleLastPageButtonClick = event => {
|
||||
onChangePage(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<IconButton
|
||||
onClick={handleFirstPageButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label="first page"
|
||||
>
|
||||
{theme.direction === 'rtl' ? <LastPageIcon /> : <FirstPageIcon />}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={handleBackButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label="previous page"
|
||||
>
|
||||
{theme.direction === 'rtl' ? (
|
||||
<KeyboardArrowRight />
|
||||
) : (
|
||||
<KeyboardArrowLeft />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={handleNextButtonClick}
|
||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
||||
aria-label="next page"
|
||||
>
|
||||
{theme.direction === 'rtl' ? (
|
||||
<KeyboardArrowLeft />
|
||||
) : (
|
||||
<KeyboardArrowRight />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={handleLastPageButtonClick}
|
||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
||||
aria-label="last page"
|
||||
>
|
||||
{theme.direction === 'rtl' ? <FirstPageIcon /> : <LastPageIcon />}
|
||||
</IconButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
TablePaginationActions.propTypes = {
|
||||
count: PropTypes.number.isRequired,
|
||||
onChangePage: PropTypes.func.isRequired,
|
||||
page: PropTypes.number.isRequired,
|
||||
rowsPerPage: PropTypes.number.isRequired,
|
||||
}
|
||||
|
||||
export default TablePaginationActions
|
||||
@ -0,0 +1,91 @@
|
||||
import React from 'react'
|
||||
|
||||
import AddUserDialog from './AddUserDialog'
|
||||
import clsx from 'clsx'
|
||||
import DeleteIcon from '@material-ui/icons/Delete'
|
||||
import GlobalFilter from './GlobalFilter'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import { lighten, makeStyles } from '@material-ui/core/styles'
|
||||
import PropTypes from 'prop-types'
|
||||
import Toolbar from '@material-ui/core/Toolbar'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import Tooltip from '@material-ui/core/Tooltip'
|
||||
|
||||
const useToolbarStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(1),
|
||||
},
|
||||
highlight:
|
||||
theme.palette.type === 'light'
|
||||
? {
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: lighten(theme.palette.secondary.light, 0.85),
|
||||
}
|
||||
: {
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.secondary.dark,
|
||||
},
|
||||
title: {
|
||||
flex: '1 1 100%',
|
||||
},
|
||||
}))
|
||||
|
||||
const TableToolbar = props => {
|
||||
const classes = useToolbarStyles()
|
||||
const {
|
||||
numSelected,
|
||||
addUserHandler,
|
||||
deleteUserHandler,
|
||||
preGlobalFilteredRows,
|
||||
setGlobalFilter,
|
||||
globalFilter,
|
||||
} = props
|
||||
return (
|
||||
<Toolbar
|
||||
className={clsx(classes.root, {
|
||||
[classes.highlight]: numSelected > 0,
|
||||
})}
|
||||
>
|
||||
<AddUserDialog addUserHandler={addUserHandler} />
|
||||
{numSelected > 0 ? (
|
||||
<Typography
|
||||
className={classes.title}
|
||||
color="inherit"
|
||||
variant="subtitle1"
|
||||
>
|
||||
{numSelected} selected
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography className={classes.title} variant="h6" id="tableTitle">
|
||||
Users
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{numSelected > 0 ? (
|
||||
<Tooltip title="Delete">
|
||||
<IconButton aria-label="delete" onClick={deleteUserHandler}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<GlobalFilter
|
||||
preGlobalFilteredRows={preGlobalFilteredRows}
|
||||
globalFilter={globalFilter}
|
||||
setGlobalFilter={setGlobalFilter}
|
||||
/>
|
||||
)}
|
||||
</Toolbar>
|
||||
)
|
||||
}
|
||||
|
||||
TableToolbar.propTypes = {
|
||||
numSelected: PropTypes.number.isRequired,
|
||||
addUserHandler: PropTypes.func.isRequired,
|
||||
deleteUserHandler: PropTypes.func.isRequired,
|
||||
setGlobalFilter: PropTypes.func.isRequired,
|
||||
preGlobalFilteredRows: PropTypes.array.isRequired,
|
||||
globalFilter: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
export default TableToolbar
|
||||
13
examples/material-UI-enhanced-table/src/index.css
Normal file
13
examples/material-UI-enhanced-table/src/index.css
Normal file
@ -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;
|
||||
}
|
||||
6
examples/material-UI-enhanced-table/src/index.js
Normal file
6
examples/material-UI-enhanced-table/src/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import './index.css'
|
||||
import App from './App'
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'))
|
||||
40
examples/material-UI-enhanced-table/src/makeData.js
Normal file
40
examples/material-UI-enhanced-table/src/makeData.js
Normal file
@ -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()
|
||||
}
|
||||
10448
examples/material-UI-enhanced-table/yarn.lock
Normal file
10448
examples/material-UI-enhanced-table/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user