├── .babelrc ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .storybook ├── addons.js └── config.js ├── .stylelintignore ├── .stylelintrc ├── .travis.yml ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── __mocks__ ├── file.mock.js └── style.mock.js ├── dist ├── export.js ├── index.css ├── index.js ├── ui.js └── utilities.js ├── package-lock.json ├── package.json ├── src ├── __tests__ │ ├── export.test.js │ ├── ignore │ │ └── constants.js │ └── index.test.js ├── export.js ├── index.css ├── index.js ├── ui.js └── utilities.js └── stories ├── Demo.stories.js ├── constants └── test-data.js └── css └── demo.css /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react", 4 | "@babel/preset-env" 5 | ], 6 | "env": { 7 | "start": { 8 | "presets": [ 9 | "react-hmre" 10 | ] 11 | }, 12 | "test": { 13 | "presets": [ 14 | "@babel/preset-react", 15 | "@babel/preset-env" 16 | ] 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": ["airbnb", "plugin:flowtype/recommended"], 4 | "plugins": ["flowtype", "jest"], 5 | "rules": { 6 | "max-len": 0, 7 | "arrow-parens": ["error", "as-needed"], 8 | "no-confusing-arrow": ["error", {"allowParens": true}], 9 | "object-curly-newline": ["error", { "consistent": true }], 10 | "implicit-arrow-linebreak": 0, 11 | "operator-linebreak": 0, 12 | "linebreak-style": 0, 13 | "import/no-extraneous-dependencies": 0, 14 | "import/prefer-default-export": 0, 15 | "react/forbid-prop-types": 0, 16 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], 17 | "react/jsx-props-no-spreading": 0, 18 | "jsx-a11y/label-has-associated-control": 0 19 | }, 20 | "env": { 21 | "jest": true, 22 | "browser": true 23 | }, 24 | "settings": { 25 | "import/resolver": { 26 | "node": { 27 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | node_modules 3 | dist/__tests__/**/*.js 4 | scripts/flow/*/.flowconfig 5 | *~ 6 | *.pyc 7 | .grunt 8 | _SpecRunner.html 9 | __benchmarks__ 10 | build/ 11 | remote-repo/ 12 | coverage/ 13 | .module-cache 14 | fixtures/dom/public/react-dom.js 15 | fixtures/dom/public/react.js 16 | test/the-files-to-test.generated.js 17 | *.log* 18 | chrome-user-data 19 | *.sublime-project 20 | *.sublime-workspace 21 | .idea 22 | *.iml 23 | *.swp 24 | *.swo 25 | 26 | 27 | packages/react-devtools-core/dist 28 | packages/react-devtools-extensions/chrome/build 29 | packages/react-devtools-extensions/chrome/*.crx 30 | packages/react-devtools-extensions/chrome/*.pem 31 | packages/react-devtools-extensions/firefox/build 32 | packages/react-devtools-extensions/firefox/*.xpi 33 | packages/react-devtools-extensions/firefox/*.pem 34 | packages/react-devtools-extensions/shared/build 35 | packages/react-devtools-inline/dist 36 | packages/react-devtools-shell/dist -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | demo 3 | src 4 | __mocks__ 5 | .storybook 6 | .vscode 7 | stories 8 | .babelrc 9 | .eslintignore 10 | .eslintrc.json 11 | .travis.yml 12 | .stylelintignore 13 | .stylelintrc 14 | dist/__tests__/**/*.js -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-actions/register'; 2 | import '@storybook/addon-links/register'; 3 | import '@storybook/addon-console'; 4 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure, addDecorator } from '@storybook/react'; 2 | import { withConsole } from '@storybook/addon-console'; 3 | 4 | // automatically import all files ending in *.stories.js 5 | configure(require.context('../stories', true, /\.stories\.js$/), module); 6 | 7 | // console config 8 | addDecorator((storyFn, context) => withConsole()(storyFn)(context)); 9 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | *.min.css -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-standard" 4 | ], 5 | "rules": { 6 | "declaration-colon-newline-after": null, 7 | "value-list-max-empty-lines": null, 8 | }, 9 | "syntax": "scss" 10 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - lts/* -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.detectIndentation": false 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Barış Ateş 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-data-table-component-extensions 2 | Export table data as a CSV or Excel file, filter and print the data. 3 | 4 | [![npm package][npm-image]][npm-url] 5 | [![Build Status][travis-image]][travis-url] 6 | [![Dependencies Status][david-image]][david-url] 7 | [![Package Size][bundlephobia-image]][bundlephobia-url] 8 | 9 | ## Getting started 10 | 11 | #### Install with NPM: 12 | 13 | [Install the data-table component first,](https://github.com/jbetancur/react-data-table-component "Install the data-table component first.") 14 | ``` 15 | $ npm install react-data-table-component styled-components 16 | ``` 17 | 18 | then install the data-table-extensions extension. 19 | ``` 20 | $ npm install react-data-table-component-extensions 21 | ``` 22 | 23 | #### Usage 24 | 25 | **Live Demo [CodeSandbox](https://codesandbox.io/s/data-table-extensions-qxpv4?fontsize=14 "CodeSandbox")** 26 | 27 | 28 | ##### Features 29 | - Export the file in \*.csv and \*.xls format. 30 | - Print the table data. 31 | - Filter table data by all columns. 32 | - Filter table by digit length. Default value is 2. 33 | 34 | #### Example 35 | Example of filtering table data and export, print buttons. 36 | 37 | ![Default Theme](http://barisates.com/git/dt/extensions.jpg?h "Example") 38 | 39 | ```jsx 40 | // App.js 41 | import React from 'react'; 42 | import DataTable from 'react-data-table-component'; 43 | import DataTableExtensions from 'react-data-table-component-extensions'; 44 | import 'react-data-table-component-extensions/dist/index.css'; 45 | import { columns, data } from './Data.js'; 46 | 47 | function App() { 48 | const tableData = { 49 | columns, 50 | data, 51 | }; 52 | 53 | return ( 54 | 57 | 64 | 65 | ); 66 | } 67 | 68 | export default App; 69 | ``` 70 | ```jsx 71 | // Data.js 72 | export const columns = [ 73 | { 74 | name: 'Title', 75 | selector: 'title', 76 | sortable: true, 77 | }, 78 | { 79 | name: 'Director', 80 | selector: 'director', 81 | sortable: true, 82 | }, 83 | { 84 | name: 'Genres', 85 | selector: 'genres', 86 | sortable: true, 87 | cell: d => {d.genres.join(', ')}, 88 | }, 89 | { 90 | name: 'Year', 91 | selector: 'year', 92 | sortable: true, 93 | }, 94 | ]; 95 | 96 | export const data = [ 97 | { 98 | title: 'Beetlejuice', 99 | year: '1988', 100 | genres: [ 101 | 'Comedy', 102 | 'Fantasy', 103 | ], 104 | director: 'Tim Burton', 105 | }, 106 | { 107 | id: 2, 108 | title: 'The Cotton Club', 109 | year: '1984', 110 | runtime: '127', 111 | genres: [ 112 | 'Crime', 113 | 'Drama', 114 | 'Music', 115 | ], 116 | director: 'Francis Ford Coppola', 117 | }]; 118 | ``` 119 | #### Properties 120 | 121 | Descriptions and configuration settings for component properties. 122 | 123 | | Property | Type | Required | Default | Description | 124 | |--------------------------|---------------------|----------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 125 | | columns | array | yes | [ ] | Table column configuration | 126 | | data | array | no | [ ] | Table data | 127 | | filter | bool | no | true | Enable input filter | 128 | | filterPlaceholder | string | no | Filter Table | Default placeholder for the filter field | 129 | | **filterHidden** | bool | no | true | Filter hidden fields | 130 | | export | bool | no | true | Enable export button | 131 | | print | bool | no | true | Enable print button | 132 | | exportHeaders | bool | no | false | Exports data with table headers | 133 | | filterDigit | number | no | 2 | Number of digts to use in search. | 134 | | fileName | string | no | document.title | Set exported csv and excel file name | 135 | 136 | 137 | ##### Column Properties 138 | | Property | Type | Required | Description | 139 | |--------------------------|---------------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 140 | | cellExport | func | no | Export configuration `row => ({Title: row.Title, Example: row.Example})` | 141 | ------------ 142 | #### Author 143 | 144 | **Barış Ateş** 145 | - http://barisates.com 146 | - [github/barisates](https://github.com/barisates "github/barisates") 147 | 148 | [npm-image]:https://img.shields.io/npm/v/react-data-table-component-extensions.svg 149 | [npm-url]:https://www.npmjs.com/package/react-data-table-component-extensions 150 | [travis-image]:https://travis-ci.org/barisates/react-data-table-component-extensions.svg?branch=master 151 | [travis-url]:https://travis-ci.org/barisates/react-data-table-component-extensions 152 | [david-image]:https://david-dm.org/barisates/react-data-table-component-extensions.svg 153 | [david-url]:https://david-dm.org/barisates/react-data-table-component-extensions 154 | [bundlephobia-image]:https://badgen.net/bundlephobia/minzip/react-data-table-component-extensions 155 | [bundlephobia-url]:https://bundlephobia.com/result?p=react-data-table-component-extensions 156 | -------------------------------------------------------------------------------- /__mocks__/file.mock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /__mocks__/style.mock.js: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /dist/export.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var _utilities = _interopRequireDefault(require("./utilities")); 9 | 10 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 11 | 12 | var csv = function csv(data, header) { 13 | var contentHeader = header ? "".concat(header.map(function (e) { 14 | return e.name; 15 | }).join(';'), "\n") : ''; 16 | var content = "".concat(contentHeader).concat(data.map(function (e) { 17 | return _utilities["default"].concat.csv(e); 18 | }).join('\n')); 19 | return { 20 | content: content, 21 | type: 'text/csv', 22 | name: "".concat(document.title, ".csv") 23 | }; 24 | }; 25 | 26 | var excel = function excel(data, header) { 27 | var contentHeader = header ? "".concat(header.map(function (e) { 28 | return e.name; 29 | }).join(''), "") : ''; 30 | var contentBody = data.map(function (e) { 31 | return _utilities["default"].concat.excel(e); 32 | }); 33 | var content = "".concat(contentHeader, "").concat(contentBody.join(''), "
"); 34 | return { 35 | content: content, 36 | type: 'application/vnd.ms-excel', 37 | name: "".concat(document.title, ".xls") 38 | }; 39 | }; 40 | 41 | var print = function print(data, header) { 42 | var _excel = excel(data, header), 43 | content = _excel.content; 44 | 45 | var style = '\n' + 'body, table { \n' + 'font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', \'Roboto\', \'Oxygen\', \'Ubuntu\', \'Cantarell\', \'Fira Sans\', \'Droid Sans\', \'Helvetica Neue\', sans-serif; \n' + 'font-size:12px \n' + '}\n' + 'table {\n' + 'width: 100%;\n' + '}\n' + 'thead {\n' + 'font-weight: bold;\n' + '}'; 46 | return "").concat(content); 47 | }; 48 | 49 | var ExportMethod = { 50 | csv: csv, 51 | excel: excel, 52 | print: print 53 | }; 54 | var _default = ExportMethod; 55 | exports["default"] = _default; -------------------------------------------------------------------------------- /dist/index.css: -------------------------------------------------------------------------------- 1 | .data-table-extensions { 2 | display: inline-block; 3 | width: 100%; 4 | box-sizing: border-box; 5 | padding: 0.7rem 1.2rem; 6 | } 7 | 8 | .data-table-extensions > .data-table-extensions-filter { 9 | float: left; 10 | } 11 | 12 | .data-table-extensions > .data-table-extensions-filter > .icon { 13 | float: left; 14 | display: block; 15 | width: 20px; 16 | height: 24px; 17 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAB80lEQVQ4T73Uv2tTURQH8O85972kxpqWNG2gOKhb21X8hXYQxCEKLp3cXNw6OIm0zTWWVjo4Cv4BIlIEF4c6FEQQi4uLZNTFYvNDiwWN9713jrxKQZOXBNLiXd89H77n3Hcv4YAXHbCH/wuOWh3kKJhSEDvjVbYtbffqKDFhrqRZ1ui+IrrB4PQfRCIieorAv1Vdoq1OcBsYY4jCV6Q0aAxuS9O8TGUQhRJeUGBZoTmQf65+lzaT0DYwvxA+1AiXBsSc+bxMjb+Lxq1mnITrpFKr3Utf7QnGMxNxdQNzvVr2niUVjFh3ioU2WMITW4uHPrbu+SdhYd6dFqK32vSy9RXaSZ6T0uiCawDmZq3srXYFx+aDs0p447N3eNPSj06Dz881a2Bvtl72nnQFh60O++LqBC5Wy/5aEpizvyaN8AcRmWwspitdwfjjWMk9VqUJn73zbSln1IxMuBcMztTK/nTPQ9kF72hBvGADoC/KOtuw/juANE5GggcMvkyq6xnjFz9ZavZMGG/Il3ScxD0C8RVAvqloRGzyAF6TaqBEFwWydoRT11rRrne5MPfzuLB/UgFPJXofz+yY1YEdcc/jpEloX49DK9qopIpYpSjuri8wLtxDAUwHnDr63dLXfYG7hzGjJjuFoT1s/2DCf9N3y51u0W9RndcVBJTNUgAAAABJRU5ErkJggg==); 18 | background-repeat: no-repeat; 19 | background-position: left center; 20 | } 21 | 22 | .data-table-extensions > .data-table-extensions-filter > .filter-text { 23 | border: 0; 24 | border-bottom: 1px solid #f5f5f5; 25 | outline: none; 26 | padding: 4px; 27 | margin-left: 4px; 28 | background-color: transparent; 29 | } 30 | 31 | .data-table-extensions > .data-table-extensions-filter > .filter-text::placeholder { 32 | color: #ccc; 33 | } 34 | 35 | .data-table-extensions > .data-table-extensions-filter > .filter-text:focus, 36 | .data-table-extensions > .data-table-extensions-filter > .filter-text:hover { 37 | border-bottom-color: #dfdfdf; 38 | } 39 | 40 | .data-table-extensions > .data-table-extensions-action { 41 | float: right; 42 | position: relative; 43 | } 44 | 45 | .data-table-extensions > .data-table-extensions-action > button { 46 | float: right; 47 | display: block; 48 | width: 30px; 49 | height: 30px; 50 | background-repeat: no-repeat; 51 | background-position: center center; 52 | background-color: transparent; 53 | border: none; 54 | cursor: pointer; 55 | padding: 5px; 56 | border-radius: 0.3rem; 57 | outline: none; 58 | margin-right: 0.3rem; 59 | } 60 | 61 | .data-table-extensions > .data-table-extensions-action > button::after { 62 | display: block; 63 | white-space: nowrap; 64 | width: 60px; 65 | margin-top: 30px; 66 | margin-left: -20px; 67 | -webkit-animation: fadeIn 0.4s; 68 | animation: fadeIn 0.4s; 69 | text-align: center; 70 | background: #f5f5f5; 71 | line-height: 24px; 72 | border-radius: 5px; 73 | font-size: 13px; 74 | color: #157efb; 75 | } 76 | 77 | .data-table-extensions > .data-table-extensions-action > button.drop, 78 | .data-table-extensions > .data-table-extensions-action > button:hover { 79 | background-color: #f5f5f5; 80 | } 81 | 82 | .data-table-extensions > .data-table-extensions-action > button.download { 83 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACQUlEQVRIS+2Vz2sTURDHZ2bfJgER0e5K8WIEwR+nCuJJISBCD14EwWsK9aYgePHQbF42Aa+CFT0UDZ4Ef9Czp+g/YNCDCApSRDSbtAGR1s3bN7Khm+5mAyaBgoJ7Wmbf+35m5vt2HsIuP7jL+vD3AeyyP+dVMs1xK5+4AtvpNTzXLPwHgFX2TxPjxXgriFQ90KLOBJI0JNokSCx/ldgebt1ID45e52x3v3pFAGVN8CvapEA0Ta1WDVJFBSIfxUnziQDpTKciFsYCHHR6Epi/taqZB+EGu+Sf8qqZN/33bZPzknM/lH+kU8u+D+Mzpd5TNOBeW5qNOCRVgbW0dQyJ7nuueR4AuS+6pFaQ+EvLNWUI2ENi/qf2XyAbT1pV8ThcY5X5ELJa3bchzn28i4OqUwDb8V8S883v1ey7QSaSaUYFKwbxmgaYR9DrwPjMq2YexrO1S/41RLTCRKJ4AmCX/CIgHvdc81bqnG9DiIIiMC4Oi/fXSyZbqwbr4Gq7lvsQhhKAGWerhWg8IobNOGCQUSgA6qwnzdfh91m5mddaFONrGfikBqSOa15OAQ44vSYR3Ihv0BrurLvm3Kg/15K9AioussB6wlgNMvrbky0aMQZCTxSaixsVXBuG2I6/AICznmveTngR0xkDoK4AcBkQniNDMBBC2KsZL5FWF1q13KepAVGvVSAOg7HjGQLoHoi3XYnddGU7A/GPFYw7NceqwHL8zwRJw6YBMEBhpMnhqZhGMDV/mLvRpTTxhTNpAv8+4DdMgQkoILVAwQAAAABJRU5ErkJggg==); 84 | } 85 | 86 | .data-table-extensions > .data-table-extensions-action > button.download.drop:hover::after { 87 | content: none; 88 | } 89 | 90 | .data-table-extensions > .data-table-extensions-action > button.download:hover::after { 91 | content: 'Export'; 92 | } 93 | 94 | .data-table-extensions > .data-table-extensions-action > button.print { 95 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACUklEQVRIS8XVTWgTQRQH8P+b2d0sDR4qlQqKCFVMsniwgog3BdGLKCilHiroSSgekqytB5Ei4ke7iYoevHkRKlXvggevop6smyjowYJYUOtBo8l+zJMVqU3ZbEOb4t4W5v1/vJnhDWGVP1rlfPxfQJtw90nBh+K6JKY9dZk+gvzmT0m70LKD1Ph0H5E4GTIexgZoYYlYCzyTj2LY+tEKaQ041QMM7PDs7NW4YqPsHpaB3qU4ONsYzfW3D0yxxACFqSWAKNAsVU4o8C+vaD34A/ytXYj962CKZWqmehnEWxVTN8BpUjBZ0Nd2b5pg/kYQb+q1zHmMkYrq5gFzwh2DlB/qhczddgPj1pkl95RisdGzsxebAef1pFSUr41YsysB0jeme8NA3qzbucGOAKZTGWKgp2HnrkeBHQdSTiVPhN56MXduVYDF29nxDpYFrHHe9ijNk6GvujQD6aRDDzzUpC5+isAIv9vbvrTsQC9Vb/ukLqBgzekl95ZkWheG2A0BSpqIrMBS4llI/NkvWmdw5VW3rhuXfDsz3HSLDMc9Jgj9Ssc99lk3JM96gbzv27m9hlM9DuZNTZ0QzXh2dlJ3Kk8NLRz0QlpPOvnwMAQSL71i9lETEP0YE+4AEe2EQgoaP1aKRiNAL7u7BLB2IaCAOb9gPY8AIfgaAjoIgQaDX3i2NT8gE9+DqDgCks5gqTWJgOlUntTt3P4kYKk1iUCq7G6BwmkCb4hDGPQRAncaBetd++N68crx6b40RC0uoAaVxsj298t60VYy8OLfg04lLsr5DVarICjeIYUiAAAAAElFTkSuQmCC); 96 | background-position: center 4px; 97 | } 98 | 99 | .data-table-extensions > .data-table-extensions-action > button.print:hover::after { 100 | content: 'Print'; 101 | } 102 | 103 | .data-table-extensions > .data-table-extensions-action > .dropdown { 104 | position: absolute; 105 | top: 100%; 106 | right: 5px; 107 | z-index: 1000; 108 | padding: 0; 109 | margin: 0.125rem 0 0; 110 | text-align: right; 111 | list-style: none; 112 | background-color: #f5f5f5; 113 | background-clip: padding-box; 114 | border: 1px solid #f5f5f5; 115 | border-radius: 0.25rem; 116 | line-height: 16px; 117 | display: none; 118 | } 119 | 120 | .data-table-extensions > .data-table-extensions-action > .dropdown.drop { 121 | -webkit-animation: fadeIn 0.3s; 122 | animation: fadeIn 0.3s; 123 | display: block; 124 | } 125 | 126 | @-webkit-keyframes fadeIn { 127 | from { 128 | opacity: 0; 129 | } 130 | 131 | to { 132 | opacity: 1; 133 | } 134 | } 135 | 136 | @keyframes fadeIn { 137 | from { 138 | opacity: 0; 139 | } 140 | 141 | to { 142 | opacity: 1; 143 | } 144 | } 145 | 146 | .data-table-extensions > .data-table-extensions-action > .dropdown button { 147 | display: block; 148 | text-decoration: none; 149 | white-space: nowrap; 150 | font-size: 13px; 151 | color: #157efb; 152 | padding: 4px 6px; 153 | background-color: transparent; 154 | border: none; 155 | width: 100%; 156 | cursor: pointer; 157 | outline: none; 158 | text-align: left; 159 | } 160 | 161 | .data-table-extensions > .data-table-extensions-action > .dropdown button:hover { 162 | background-color: #fcfcfc; 163 | } 164 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var _react = _interopRequireWildcard(require("react")); 9 | 10 | var _propTypes = _interopRequireDefault(require("prop-types")); 11 | 12 | var _ui = require("./ui"); 13 | 14 | var _utilities = _interopRequireDefault(require("./utilities")); 15 | 16 | var _export = _interopRequireDefault(require("./export")); 17 | 18 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 19 | 20 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; } 21 | 22 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; if (obj != null) { var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } 23 | 24 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 25 | 26 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 27 | 28 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 29 | 30 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 31 | 32 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 33 | 34 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 35 | 36 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 37 | 38 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 39 | 40 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 41 | 42 | var DataTableExtensions = 43 | /*#__PURE__*/ 44 | function (_Component) { 45 | _inherits(DataTableExtensions, _Component); 46 | 47 | function DataTableExtensions(props) { 48 | var _this; 49 | 50 | _classCallCheck(this, DataTableExtensions); 51 | 52 | _this = _possibleConstructorReturn(this, _getPrototypeOf(DataTableExtensions).call(this, props)); 53 | var columns = props.columns, 54 | data = props.data; 55 | _this.state = { 56 | dropdown: false, 57 | columns: columns, 58 | data: data, 59 | constData: data, 60 | filter: '' 61 | }; 62 | _this.raw = { 63 | header: [], 64 | data: [] 65 | }; 66 | return _this; 67 | } 68 | 69 | _createClass(DataTableExtensions, [{ 70 | key: "componentDidMount", 71 | value: function componentDidMount() { 72 | var _this2 = this; 73 | 74 | var columns = this.state.columns; // column properties and select fields to export 75 | 76 | columns.forEach(function (element) { 77 | if (element["export"] !== false) { 78 | _this2.raw.header.push(element); 79 | } 80 | }); 81 | } 82 | }, { 83 | key: "componentDidUpdate", 84 | value: function componentDidUpdate(prevProps) { 85 | var _this3 = this; 86 | 87 | var _this$props = this.props, 88 | columns = _this$props.columns, 89 | data = _this$props.data; 90 | var filter = this.state.filter; 91 | 92 | if (prevProps.columns !== columns || prevProps.data !== data) { 93 | // eslint-disable-next-line react/no-did-update-set-state 94 | this.setState({ 95 | columns: columns, 96 | data: data, 97 | constData: data 98 | }, function () { 99 | _this3.checkHeader(); 100 | 101 | if (filter.length > 2) { 102 | _this3.onFilter(filter); 103 | } 104 | }); 105 | } 106 | } 107 | }, { 108 | key: "onDataRender", 109 | value: function onDataRender() { 110 | var constData = this.state.constData; // get and render data 111 | 112 | this.raw.data = _utilities["default"].dataRender(constData, this.raw.header); 113 | } 114 | }, { 115 | key: "onExport", 116 | value: function onExport(e, type) { 117 | this.onDataRender(); 118 | var exportHeaders = this.props.exportHeaders; 119 | var _this$raw = this.raw, 120 | data = _this$raw.data, 121 | header = _this$raw.header; 122 | 123 | var exportData = _export["default"][type](data, exportHeaders ? header : null); 124 | 125 | _utilities["default"].download(exportData); 126 | 127 | this.setState({ 128 | dropdown: false 129 | }); 130 | e.preventDefault(); 131 | } 132 | }, { 133 | key: "onFilter", 134 | value: function onFilter(text) { 135 | var value = _utilities["default"].lower(text); 136 | 137 | var constData = this.state.constData; 138 | var filterHidden = this.props.filterHidden; 139 | var filtered = constData; 140 | 141 | if (value.length > 2) { 142 | if (!filterHidden) { 143 | this.onDataRender(); 144 | } 145 | 146 | filtered = _utilities["default"].filter(value, constData, this.raw.data, filterHidden); 147 | } 148 | 149 | this.setState({ 150 | data: filtered, 151 | filter: value 152 | }); 153 | } 154 | }, { 155 | key: "onPrint", 156 | value: function onPrint() { 157 | this.onDataRender(); 158 | var _this$raw2 = this.raw, 159 | data = _this$raw2.data, 160 | header = _this$raw2.header; 161 | 162 | var table = _export["default"].print(data, header); 163 | 164 | _utilities["default"].print(table); 165 | } 166 | }, { 167 | key: "checkHeader", 168 | value: function checkHeader() { 169 | var _this4 = this; 170 | 171 | var columns = this.state.columns; 172 | 173 | if (columns.length !== this.raw.header.length) { 174 | this.raw.header = []; 175 | columns.forEach(function (element) { 176 | if (element["export"] !== false) { 177 | _this4.raw.header.push(element); 178 | } 179 | }); 180 | } 181 | } 182 | }, { 183 | key: "render", 184 | value: function render() { 185 | var _this5 = this; 186 | 187 | var _this$state = this.state, 188 | dropdown = _this$state.dropdown, 189 | columns = _this$state.columns, 190 | data = _this$state.data; 191 | var _this$props2 = this.props, 192 | filter = _this$props2.filter, 193 | print = _this$props2.print, 194 | children = _this$props2.children, 195 | filterPlaceholder = _this$props2.filterPlaceholder; 196 | return _react["default"].createElement(_react["default"].Fragment, null, _react["default"].createElement("div", { 197 | className: "data-table-extensions" 198 | }, filter && _react["default"].createElement(_ui.Filter, { 199 | onChange: function onChange(e) { 200 | return _this5.onFilter(e.target.value); 201 | }, 202 | placeholder: filterPlaceholder 203 | }), _react["default"].createElement("div", { 204 | className: "data-table-extensions-action" 205 | }, this.props["export"] && _react["default"].createElement(_ui.Export, { 206 | className: dropdown ? 'drop' : '', 207 | onDropdown: function onDropdown() { 208 | return _this5.setState(function (prevState) { 209 | return { 210 | dropdown: !prevState.dropdown 211 | }; 212 | }); 213 | }, 214 | onClick: function onClick(e, type) { 215 | return _this5.onExport(e, type); 216 | } 217 | }), print && _react["default"].createElement(_ui.Print, { 218 | onClick: function onClick() { 219 | return _this5.onPrint(); 220 | } 221 | }))), _react["default"].cloneElement(children, { 222 | columns: columns, 223 | data: data 224 | })); 225 | } 226 | }]); 227 | 228 | return DataTableExtensions; 229 | }(_react.Component); 230 | 231 | DataTableExtensions.propTypes = { 232 | columns: _propTypes["default"].array, 233 | data: _propTypes["default"].array, 234 | filter: _propTypes["default"].bool, 235 | filterPlaceholder: _propTypes["default"].string, 236 | "export": _propTypes["default"].bool, 237 | print: _propTypes["default"].bool, 238 | exportHeaders: _propTypes["default"].bool, 239 | children: _propTypes["default"].node, 240 | filterHidden: _propTypes["default"].bool 241 | }; 242 | DataTableExtensions.defaultProps = { 243 | columns: [], 244 | data: [], 245 | filter: true, 246 | "export": true, 247 | print: true, 248 | exportHeaders: false, 249 | children: null, 250 | filterHidden: true, 251 | filterPlaceholder: 'Filter Table' 252 | }; 253 | var _default = DataTableExtensions; 254 | exports["default"] = _default; -------------------------------------------------------------------------------- /dist/ui.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Print = exports.Export = exports.Filter = void 0; 7 | 8 | var _react = _interopRequireDefault(require("react")); 9 | 10 | var _propTypes = _interopRequireDefault(require("prop-types")); 11 | 12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 13 | 14 | var Filter = function Filter(_ref) { 15 | var _onChange = _ref.onChange, 16 | placeholder = _ref.placeholder; 17 | return _react["default"].createElement("div", { 18 | className: "data-table-extensions-filter" 19 | }, _react["default"].createElement("label", { 20 | htmlFor: "filterDataTable", 21 | className: "icon" 22 | }), _react["default"].createElement("input", { 23 | type: "text", 24 | name: "filterDataTable", 25 | className: "filter-text", 26 | placeholder: placeholder, 27 | onChange: function onChange(e) { 28 | return _onChange(e); 29 | } 30 | })); 31 | }; 32 | 33 | exports.Filter = Filter; 34 | Filter.propTypes = { 35 | onChange: _propTypes["default"].func, 36 | placeholder: _propTypes["default"].string.isRequired 37 | }; 38 | Filter.defaultProps = { 39 | onChange: null 40 | }; 41 | 42 | var Export = function Export(props) { 43 | var className = props.className, 44 | onDropdown = props.onDropdown, 45 | _onClick = props.onClick; 46 | return _react["default"].createElement(_react["default"].Fragment, null, _react["default"].createElement("button", { 47 | type: "button", 48 | className: "download ".concat(className), 49 | onClick: function onClick() { 50 | return onDropdown(); 51 | } 52 | }), _react["default"].createElement("div", { 53 | className: "dropdown ".concat(className) 54 | }, _react["default"].createElement("button", { 55 | type: "button", 56 | onClick: function onClick(e) { 57 | return _onClick(e, 'csv'); 58 | } 59 | }, "Csv File"), _react["default"].createElement("button", { 60 | type: "button", 61 | onClick: function onClick(e) { 62 | return _onClick(e, 'excel'); 63 | } 64 | }, "Excel File"))); 65 | }; 66 | 67 | exports.Export = Export; 68 | Export.propTypes = { 69 | className: _propTypes["default"].string, 70 | onDropdown: _propTypes["default"].func, 71 | onClick: _propTypes["default"].func 72 | }; 73 | Export.defaultProps = { 74 | className: '', 75 | onDropdown: null, 76 | onClick: null 77 | }; 78 | 79 | var Print = function Print(props) { 80 | return (// eslint-disable-next-line jsx-a11y/control-has-associated-label 81 | _react["default"].createElement("button", { 82 | type: "button", 83 | className: "print", 84 | onClick: function onClick() { 85 | return props.onClick(); 86 | } 87 | }) 88 | ); 89 | }; 90 | 91 | exports.Print = Print; 92 | Print.propTypes = { 93 | onClick: _propTypes["default"].func 94 | }; 95 | Print.defaultProps = { 96 | onClick: null 97 | }; -------------------------------------------------------------------------------- /dist/utilities.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | 8 | var _reactDom = require("react-dom"); 9 | 10 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 11 | 12 | var download = function download(props) { 13 | var content = props.content, 14 | type = props.type, 15 | name = props.name; 16 | var file = new Blob(["\uFEFF", content], { 17 | type: type 18 | }); 19 | var link = document.createElement('a'); 20 | link.id = "_export_datatable_".concat(name); 21 | link.download = name; 22 | link.href = window.URL.createObjectURL(file); 23 | document.body.appendChild(link); 24 | link.click(); 25 | document.getElementById(link.id).remove(); 26 | }; 27 | 28 | var print = function print(table) { 29 | var printWindow = window.open(); 30 | printWindow.document.write(table); 31 | printWindow.print(); 32 | printWindow.close(); 33 | }; 34 | 35 | var lower = function lower(value) { 36 | return value.toString().toLowerCase(); 37 | }; 38 | 39 | var objectValues = function objectValues(item) { 40 | return Object.values(item).map(function (obj) { 41 | return _typeof(obj) === 'object' && obj !== null ? objectValues(obj) : obj; 42 | }); 43 | }; 44 | 45 | var filter = function filter(search, constant, data, filterHidden) { 46 | return constant.filter(function (item, index) { 47 | var value = (filterHidden ? objectValues(item) : Object.values(data[index])).join(); 48 | var searchSplit = search.split(' ').filter(function (filterItem) { 49 | return filterItem !== ''; 50 | }); 51 | return searchSplit.filter(function (filterItem) { 52 | return lower(value).indexOf(filterItem.trim()) !== -1; 53 | }).length === searchSplit.length; // return (lower(value).indexOf(search.trim()) !== -1); 54 | // const found = data[index].filter(f => (lower(f).indexOf(search) !== -1)); 55 | // return (found.length > 0); 56 | }); 57 | }; 58 | 59 | var getProperty = function getProperty(row, selector, format) { 60 | if (typeof selector !== 'string') { 61 | throw new Error('selector must be a . delimted string eg (my.property)'); 62 | } 63 | 64 | if (format && typeof format === 'function') { 65 | return format(row); 66 | } 67 | 68 | return selector.split('.').reduce(function (acc, part) { 69 | if (!acc) { 70 | return null; 71 | } // O(n2) when querying for an array (e.g. items[0].name) 72 | // Likely, the object depth will be reasonable enough that performance is not a concern 73 | 74 | 75 | var arr = part.match(/[^\]\\[.]+/g); 76 | 77 | if (arr.length > 1) { 78 | // eslint-disable-next-line no-plusplus 79 | for (var i = 0; i < arr.length; i++) { 80 | return acc[arr[i]][arr[i + 1]]; 81 | } 82 | } 83 | 84 | return acc[part]; 85 | }, row); 86 | }; 87 | 88 | var dataRender = function dataRender(data, header) { 89 | var rawData = []; // get and render data 90 | 91 | data.forEach(function (element) { 92 | var row = []; 93 | header.forEach(function (head) { 94 | // Export Cell 95 | if (head.cellExport) { 96 | var exportData = head.cellExport(element); 97 | row.push(exportData); // row.push(`${Object.keys(exportData).map(key => ``).join('')}
${key}${exportData[key].toString()}
`); 98 | } else if (head.cell) { 99 | // cell: render component and get innerText 100 | var div = document.createElement('div'); 101 | (0, _reactDom.render)(head.cell(element), div); 102 | row.push(div.innerText); 103 | (0, _reactDom.unmountComponentAtNode)(div); 104 | } else { 105 | // get property 106 | row.push(getProperty(element, head.selector, head.format)); 107 | } 108 | }); 109 | rawData.push(row); 110 | }); 111 | return rawData; 112 | }; 113 | 114 | var concat = { 115 | csv: function csv(row) { 116 | var items = []; 117 | row.forEach(function (item) { 118 | if (_typeof(item) === 'object' && item !== null) { 119 | items.push(Object.keys(item).map(function (key) { 120 | return "".concat(key, ": ").concat(item[key]); 121 | }).join(';')); 122 | } else { 123 | items.push(item); 124 | } 125 | }); 126 | return items.join(';'); 127 | }, 128 | excel: function excel(row) { 129 | var items = []; 130 | row.forEach(function (item) { 131 | if (_typeof(item) === 'object' && item !== null) { 132 | items.push("".concat(Object.keys(item).map(function (key) { 133 | return ""); 134 | }).join(''), "
".concat(key, "").concat(item[key], "
")); 135 | } else { 136 | items.push(item); 137 | } 138 | }); 139 | return "".concat(items.join(''), ""); 140 | } 141 | }; 142 | var Utilities = { 143 | download: download, 144 | print: print, 145 | filter: filter, 146 | getProperty: getProperty, 147 | lower: lower, 148 | dataRender: dataRender, 149 | concat: concat 150 | }; 151 | var _default = Utilities; 152 | exports["default"] = _default; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-data-table-component-extensions", 3 | "version": "1.5.2", 4 | "description": "Export table data as a CSV or Excel file, filter and print the data.", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "start": "npm run storybook", 8 | "test": "npm run lint && jest", 9 | "build": "npm run lint && babel src --out-dir dist --copy-files", 10 | "build:minify": "npm run lint && babel --minified src --out-dir dist --copy-files", 11 | "storybook": "start-storybook -p 6006", 12 | "build-storybook": "build-storybook", 13 | "lint": "npm run lint:css && npm run lint:js", 14 | "lint:css": "stylelint ./src/**/*.css", 15 | "lint:js": "eslint ./src/**/*.js" 16 | }, 17 | "jest": { 18 | "testPathIgnorePatterns": [ 19 | "node_modules/", 20 | "src/__tests__/ignore", 21 | "dist/__tests__" 22 | ], 23 | "moduleNameMapper": { 24 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/file.mock.js", 25 | "\\.(css|less)$": "/__mocks__/style.mock.js" 26 | } 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/barisates/react-data-table-component-extensions.git" 31 | }, 32 | "keywords": [ 33 | "react", 34 | "reactjs", 35 | "react-component", 36 | "datatable", 37 | "extensions" 38 | ], 39 | "author": { 40 | "name": "Barış Ateş", 41 | "email": "info@barisates.com", 42 | "url": "http://barisates.com" 43 | }, 44 | "license": "MIT", 45 | "bugs": { 46 | "url": "https://github.com/barisates/react-data-table-component-extensions/issues" 47 | }, 48 | "homepage": "https://github.com/barisates/react-data-table-component-extensions#readme", 49 | "devDependencies": { 50 | "@babel/cli": "^7.6.0", 51 | "@babel/core": "^7.6.0", 52 | "@babel/preset-env": "^7.6.0", 53 | "@babel/preset-react": "^7.0.0", 54 | "@storybook/addon-actions": "^5.2.1", 55 | "@storybook/addon-console": "^1.2.1", 56 | "@storybook/addon-links": "^5.2.1", 57 | "@storybook/addons": "^5.2.1", 58 | "@storybook/react": "^5.2.1", 59 | "babel-loader": "^8.0.6", 60 | "react-data-table-component": "^3.5.1", 61 | "styled-components": "^4.3.2", 62 | "eslint": "^6.2.2", 63 | "eslint-config-airbnb": "^18.0.1", 64 | "eslint-config-react-app": "^5.0.1", 65 | "eslint-plugin-flowtype": "^4.2.0", 66 | "eslint-plugin-import": "^2.18.2", 67 | "eslint-plugin-jest": "^22.15.2", 68 | "eslint-plugin-react": "^7.14.3", 69 | "prop-types": "^15.7.2", 70 | "react-dom": "latest", 71 | "stylelint": "^10.1.0", 72 | "stylelint-config-standard": "^18.3.0", 73 | "babel-eslint": "^10.0.3", 74 | "eslint-plugin-jsx-a11y": "^6.2.3", 75 | "jest": "^24.9.0" 76 | }, 77 | "dependencies": { 78 | "react": "^16.9.0" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/__tests__/export.test.js: -------------------------------------------------------------------------------- 1 | import { columns, data } from './ignore/constants'; 2 | import Utilities from '../utilities'; 3 | import ExportMethod from '../export'; 4 | 5 | const raw = { 6 | header: [], 7 | data: [], 8 | }; 9 | 10 | const render = () => { 11 | // column properties and select fields to export 12 | columns.forEach(element => { 13 | if (element.export !== false) { 14 | raw.header.push(element); 15 | } 16 | }); 17 | 18 | // get and render data 19 | raw.data = Utilities.dataRender(data, raw.header); 20 | }; 21 | 22 | it('export without crashing: excel', () => { 23 | render(); 24 | 25 | const excelData = ExportMethod.excel(raw.data, raw.header); 26 | 27 | if (excelData === null) { 28 | throw new Error(); 29 | } 30 | }); 31 | 32 | it('export without crashing: csv', () => { 33 | render(); 34 | 35 | const csvData = ExportMethod.csv(raw.data, raw.header); 36 | 37 | if (csvData === null) { 38 | throw new Error(); 39 | } 40 | }); 41 | 42 | it('export without crashing: print', () => { 43 | render(); 44 | 45 | const printData = ExportMethod.print(raw.data, raw.header); 46 | 47 | if (printData === null) { 48 | throw new Error(); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /src/__tests__/ignore/constants.js: -------------------------------------------------------------------------------- 1 | export const columns = [ 2 | { 3 | name: 'Title', 4 | selector: 'title', 5 | sortable: true, 6 | }, 7 | { 8 | name: 'Director', 9 | selector: 'director', 10 | sortable: true, 11 | }, 12 | { 13 | name: 'Genres', 14 | selector: 'genres', 15 | sortable: true, 16 | }, 17 | { 18 | name: 'Year', 19 | selector: 'year', 20 | sortable: true, 21 | }, 22 | ]; 23 | 24 | export const data = [ 25 | { 26 | id: 1, 27 | title: 'Beetlejuice', 28 | year: '1988', 29 | runtime: '92', 30 | genres: [ 31 | 'Comedy', 32 | 'Fantasy', 33 | ], 34 | director: 'Tim Burton', 35 | actors: 'Alec Baldwin, Geena Davis, Annie McEnroe, Maurice Page', 36 | plot: 'A couple of recently deceased ghosts contract the services of a "bio-exorcist" in order to remove the obnoxious new owners of their house.', 37 | posterUrl: 'https://images-na.ssl-images-amazon.com/images/M/MV5BMTUwODE3MDE0MV5BMl5BanBnXkFtZTgwNTk1MjI4MzE@._V1_SX300.jpg', 38 | }, 39 | { 40 | id: 2, 41 | title: 'The Cotton Club', 42 | year: '1984', 43 | runtime: '127', 44 | genres: [ 45 | 'Crime', 46 | 'Drama', 47 | 'Music', 48 | ], 49 | director: null, 50 | actors: 'Richard Gere, Gregory Hines, Diane Lane, Lonette McKee', 51 | plot: 'The Cotton Club was a famous night club in Harlem. The story follows the people that visited the club, those that ran it, and is peppered with the Jazz music that made it so famous.', 52 | posterUrl: 'https://images-na.ssl-images-amazon.com/images/M/MV5BMTU5ODAyNzA4OV5BMl5BanBnXkFtZTcwNzYwNTIzNA@@._V1_SX300.jpg', 53 | }]; 54 | -------------------------------------------------------------------------------- /src/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import DataTable from 'react-data-table-component'; 4 | import DataTableExtensions from '../index'; 5 | import { columns, data } from './ignore/constants'; 6 | 7 | it('renders without crashing: null data', () => { 8 | const div = document.createElement('div'); 9 | ReactDOM.render(, div); 10 | ReactDOM.unmountComponentAtNode(div); 11 | }); 12 | 13 | 14 | it('renders without crashing: test data', () => { 15 | const div = document.createElement('div'); 16 | ReactDOM.render( 17 | 21 | 22 | , 23 | div, 24 | ); 25 | ReactDOM.unmountComponentAtNode(div); 26 | }); 27 | 28 | it('renders without crashing: filter extensions disabled', () => { 29 | const div = document.createElement('div'); 30 | ReactDOM.render( 31 | 38 | 39 | , 40 | div, 41 | ); 42 | ReactDOM.unmountComponentAtNode(div); 43 | }); 44 | -------------------------------------------------------------------------------- /src/export.js: -------------------------------------------------------------------------------- 1 | import Utilities from './utilities'; 2 | 3 | const csv = (data, header, fileName) => { 4 | const contentHeader = (header ? `${header.map(e => e.name).join(';')}\n` : ''); 5 | const content = `${contentHeader}${data.map(e => Utilities.concat.csv(e)).join('\n')}`; 6 | 7 | return { 8 | content, 9 | type: 'text/csv', 10 | name: `${fileName || document.title}.csv`, 11 | }; 12 | }; 13 | 14 | const excel = (data, header, fileName) => { 15 | const contentHeader = (header ? `${header.map(e => e.name).join('')}` : ''); 16 | const contentBody = data.map(e => Utilities.concat.excel(e)); 17 | const content = `${contentHeader}${contentBody.join('')}
`; 18 | 19 | return { 20 | content, 21 | type: 'application/vnd.ms-excel', 22 | name: `${fileName || document.title}.xls`, 23 | }; 24 | }; 25 | 26 | const print = (data, header) => { 27 | const { content } = excel(data, header); 28 | 29 | const style = '\n' + 30 | 'body, table { \n' + 31 | 'font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', \'Roboto\', \'Oxygen\', \'Ubuntu\', \'Cantarell\', \'Fira Sans\', \'Droid Sans\', \'Helvetica Neue\', sans-serif; \n' + 32 | 'font-size:12px \n' + 33 | '}\n' + 34 | 'table {\n' + 35 | 'width: 100%;\n' + 36 | '}\n' + 37 | 'thead {\n' + 38 | 'font-weight: bold;\n' + 39 | '}'; 40 | return `${content}`; 41 | }; 42 | 43 | const ExportMethod = { 44 | csv, 45 | excel, 46 | print, 47 | }; 48 | 49 | export default ExportMethod; 50 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | .data-table-extensions { 2 | display: inline-block; 3 | width: 100%; 4 | box-sizing: border-box; 5 | padding: 0.7rem 1.2rem; 6 | } 7 | 8 | .data-table-extensions > .data-table-extensions-filter { 9 | float: left; 10 | } 11 | 12 | .data-table-extensions > .data-table-extensions-filter > .icon { 13 | float: left; 14 | display: block; 15 | width: 20px; 16 | height: 24px; 17 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAB80lEQVQ4T73Uv2tTURQH8O85972kxpqWNG2gOKhb21X8hXYQxCEKLp3cXNw6OIm0zTWWVjo4Cv4BIlIEF4c6FEQQi4uLZNTFYvNDiwWN9713jrxKQZOXBNLiXd89H77n3Hcv4YAXHbCH/wuOWh3kKJhSEDvjVbYtbffqKDFhrqRZ1ui+IrrB4PQfRCIieorAv1Vdoq1OcBsYY4jCV6Q0aAxuS9O8TGUQhRJeUGBZoTmQf65+lzaT0DYwvxA+1AiXBsSc+bxMjb+Lxq1mnITrpFKr3Utf7QnGMxNxdQNzvVr2niUVjFh3ioU2WMITW4uHPrbu+SdhYd6dFqK32vSy9RXaSZ6T0uiCawDmZq3srXYFx+aDs0p447N3eNPSj06Dz881a2Bvtl72nnQFh60O++LqBC5Wy/5aEpizvyaN8AcRmWwspitdwfjjWMk9VqUJn73zbSln1IxMuBcMztTK/nTPQ9kF72hBvGADoC/KOtuw/juANE5GggcMvkyq6xnjFz9ZavZMGG/Il3ScxD0C8RVAvqloRGzyAF6TaqBEFwWydoRT11rRrne5MPfzuLB/UgFPJXofz+yY1YEdcc/jpEloX49DK9qopIpYpSjuri8wLtxDAUwHnDr63dLXfYG7hzGjJjuFoT1s/2DCf9N3y51u0W9RndcVBJTNUgAAAABJRU5ErkJggg==); 18 | background-repeat: no-repeat; 19 | background-position: left center; 20 | } 21 | 22 | .data-table-extensions > .data-table-extensions-filter > .filter-text { 23 | border: 0; 24 | border-bottom: 1px solid #f5f5f5; 25 | outline: none; 26 | padding: 4px; 27 | margin-left: 4px; 28 | background-color: transparent; 29 | } 30 | 31 | .data-table-extensions > .data-table-extensions-filter > .filter-text::placeholder { 32 | color: #ccc; 33 | } 34 | 35 | .data-table-extensions > .data-table-extensions-filter > .filter-text:focus, 36 | .data-table-extensions > .data-table-extensions-filter > .filter-text:hover { 37 | border-bottom-color: #dfdfdf; 38 | } 39 | 40 | .data-table-extensions > .data-table-extensions-action { 41 | float: right; 42 | position: relative; 43 | } 44 | 45 | .data-table-extensions > .data-table-extensions-action > button { 46 | float: right; 47 | display: block; 48 | width: 30px; 49 | height: 30px; 50 | background-repeat: no-repeat; 51 | background-position: center center; 52 | background-color: transparent; 53 | border: none; 54 | cursor: pointer; 55 | padding: 5px; 56 | border-radius: 0.3rem; 57 | outline: none; 58 | margin-right: 0.3rem; 59 | } 60 | 61 | .data-table-extensions > .data-table-extensions-action > button::after { 62 | display: block; 63 | white-space: nowrap; 64 | width: 60px; 65 | margin-top: 30px; 66 | margin-left: -20px; 67 | -webkit-animation: fadeIn 0.4s; 68 | animation: fadeIn 0.4s; 69 | text-align: center; 70 | background: #f5f5f5; 71 | line-height: 24px; 72 | border-radius: 5px; 73 | font-size: 13px; 74 | color: #157efb; 75 | } 76 | 77 | .data-table-extensions > .data-table-extensions-action > button.drop, 78 | .data-table-extensions > .data-table-extensions-action > button:hover { 79 | background-color: #f5f5f5; 80 | } 81 | 82 | .data-table-extensions > .data-table-extensions-action > button.download { 83 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACQUlEQVRIS+2Vz2sTURDHZ2bfJgER0e5K8WIEwR+nCuJJISBCD14EwWsK9aYgePHQbF42Aa+CFT0UDZ4Ef9Czp+g/YNCDCApSRDSbtAGR1s3bN7Khm+5mAyaBgoJ7Wmbf+35m5vt2HsIuP7jL+vD3AeyyP+dVMs1xK5+4AtvpNTzXLPwHgFX2TxPjxXgriFQ90KLOBJI0JNokSCx/ldgebt1ID45e52x3v3pFAGVN8CvapEA0Ta1WDVJFBSIfxUnziQDpTKciFsYCHHR6Epi/taqZB+EGu+Sf8qqZN/33bZPzknM/lH+kU8u+D+Mzpd5TNOBeW5qNOCRVgbW0dQyJ7nuueR4AuS+6pFaQ+EvLNWUI2ENi/qf2XyAbT1pV8ThcY5X5ELJa3bchzn28i4OqUwDb8V8S883v1ey7QSaSaUYFKwbxmgaYR9DrwPjMq2YexrO1S/41RLTCRKJ4AmCX/CIgHvdc81bqnG9DiIIiMC4Oi/fXSyZbqwbr4Gq7lvsQhhKAGWerhWg8IobNOGCQUSgA6qwnzdfh91m5mddaFONrGfikBqSOa15OAQ44vSYR3Ihv0BrurLvm3Kg/15K9AioussB6wlgNMvrbky0aMQZCTxSaixsVXBuG2I6/AICznmveTngR0xkDoK4AcBkQniNDMBBC2KsZL5FWF1q13KepAVGvVSAOg7HjGQLoHoi3XYnddGU7A/GPFYw7NceqwHL8zwRJw6YBMEBhpMnhqZhGMDV/mLvRpTTxhTNpAv8+4DdMgQkoILVAwQAAAABJRU5ErkJggg==); 84 | } 85 | 86 | .data-table-extensions > .data-table-extensions-action > button.download.drop:hover::after { 87 | content: none; 88 | } 89 | 90 | .data-table-extensions > .data-table-extensions-action > button.download:hover::after { 91 | content: 'Export'; 92 | } 93 | 94 | .data-table-extensions > .data-table-extensions-action > button.print { 95 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACUklEQVRIS8XVTWgTQRQH8P+b2d0sDR4qlQqKCFVMsniwgog3BdGLKCilHiroSSgekqytB5Ei4ke7iYoevHkRKlXvggevop6smyjowYJYUOtBo8l+zJMVqU3ZbEOb4t4W5v1/vJnhDWGVP1rlfPxfQJtw90nBh+K6JKY9dZk+gvzmT0m70LKD1Ph0H5E4GTIexgZoYYlYCzyTj2LY+tEKaQ041QMM7PDs7NW4YqPsHpaB3qU4ONsYzfW3D0yxxACFqSWAKNAsVU4o8C+vaD34A/ytXYj962CKZWqmehnEWxVTN8BpUjBZ0Nd2b5pg/kYQb+q1zHmMkYrq5gFzwh2DlB/qhczddgPj1pkl95RisdGzsxebAef1pFSUr41YsysB0jeme8NA3qzbucGOAKZTGWKgp2HnrkeBHQdSTiVPhN56MXduVYDF29nxDpYFrHHe9ijNk6GvujQD6aRDDzzUpC5+isAIv9vbvrTsQC9Vb/ukLqBgzekl95ZkWheG2A0BSpqIrMBS4llI/NkvWmdw5VW3rhuXfDsz3HSLDMc9Jgj9Ssc99lk3JM96gbzv27m9hlM9DuZNTZ0QzXh2dlJ3Kk8NLRz0QlpPOvnwMAQSL71i9lETEP0YE+4AEe2EQgoaP1aKRiNAL7u7BLB2IaCAOb9gPY8AIfgaAjoIgQaDX3i2NT8gE9+DqDgCks5gqTWJgOlUntTt3P4kYKk1iUCq7G6BwmkCb4hDGPQRAncaBetd++N68crx6b40RC0uoAaVxsj298t60VYy8OLfg04lLsr5DVarICjeIYUiAAAAAElFTkSuQmCC); 96 | background-position: center 4px; 97 | } 98 | 99 | .data-table-extensions > .data-table-extensions-action > button.print:hover::after { 100 | content: 'Print'; 101 | } 102 | 103 | .data-table-extensions > .data-table-extensions-action > .dropdown { 104 | position: absolute; 105 | top: 100%; 106 | right: 5px; 107 | z-index: 1000; 108 | padding: 0; 109 | margin: 0.125rem 0 0; 110 | text-align: right; 111 | list-style: none; 112 | background-color: #f5f5f5; 113 | background-clip: padding-box; 114 | border: 1px solid #f5f5f5; 115 | border-radius: 0.25rem; 116 | line-height: 16px; 117 | display: none; 118 | } 119 | 120 | .data-table-extensions > .data-table-extensions-action > .dropdown.drop { 121 | -webkit-animation: fadeIn 0.3s; 122 | animation: fadeIn 0.3s; 123 | display: block; 124 | } 125 | 126 | @-webkit-keyframes fadeIn { 127 | from { 128 | opacity: 0; 129 | } 130 | 131 | to { 132 | opacity: 1; 133 | } 134 | } 135 | 136 | @keyframes fadeIn { 137 | from { 138 | opacity: 0; 139 | } 140 | 141 | to { 142 | opacity: 1; 143 | } 144 | } 145 | 146 | .data-table-extensions > .data-table-extensions-action > .dropdown button { 147 | display: block; 148 | text-decoration: none; 149 | white-space: nowrap; 150 | font-size: 13px; 151 | color: #157efb; 152 | padding: 4px 6px; 153 | background-color: transparent; 154 | border: none; 155 | width: 100%; 156 | cursor: pointer; 157 | outline: none; 158 | text-align: left; 159 | } 160 | 161 | .data-table-extensions > .data-table-extensions-action > .dropdown button:hover { 162 | background-color: #fcfcfc; 163 | } 164 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Filter, Export, Print } from './ui'; 4 | import Utilities from './utilities'; 5 | import ExportMethod from './export'; 6 | 7 | class DataTableExtensions extends Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | const { columns, data, filterDigit } = props; 12 | 13 | this.state = { 14 | dropdown: false, 15 | columns, 16 | data, 17 | constData: data, 18 | filter: '', 19 | filterDigit, 20 | }; 21 | 22 | this.raw = { 23 | header: [], 24 | data: [], 25 | }; 26 | } 27 | 28 | componentDidMount() { 29 | const { columns } = this.state; 30 | 31 | // column properties and select fields to export 32 | columns.forEach(element => { 33 | if (element.export !== false) { 34 | this.raw.header.push(element); 35 | } 36 | }); 37 | } 38 | 39 | componentDidUpdate(prevProps) { 40 | const { columns, data, filterDigit } = this.props; 41 | const { filter } = this.state; 42 | 43 | if (prevProps.columns !== columns || prevProps.data !== data) { 44 | // eslint-disable-next-line react/no-did-update-set-state 45 | this.setState({ 46 | columns, 47 | data, 48 | filterDigit, 49 | constData: data, 50 | }, () => { 51 | this.checkHeader(); 52 | 53 | if (filter.length > filterDigit) { 54 | this.onFilter(filter); 55 | } 56 | }); 57 | } 58 | } 59 | 60 | onDataRender() { 61 | const { constData } = this.state; 62 | // get and render data 63 | this.raw.data = Utilities.dataRender(constData, this.raw.header); 64 | } 65 | 66 | onExport(e, type) { 67 | this.onDataRender(); 68 | 69 | const { exportHeaders, fileName } = this.props; 70 | const { data, header } = this.raw; 71 | 72 | const exportData = ExportMethod[type](data, (exportHeaders ? header : null), fileName); 73 | 74 | Utilities.download(exportData); 75 | 76 | this.setState({ dropdown: false }); 77 | 78 | e.preventDefault(); 79 | } 80 | 81 | onFilter(text) { 82 | const value = Utilities.lower(text); 83 | 84 | const { constData, filterDigit } = this.state; 85 | const { filterHidden } = this.props; 86 | 87 | let filtered = constData; 88 | if (value.length > filterDigit) { 89 | if (!filterHidden) { 90 | this.onDataRender(); 91 | } 92 | filtered = Utilities.filter(value, constData, this.raw.data, filterHidden); 93 | } 94 | 95 | this.setState({ data: filtered, filter: value }); 96 | } 97 | 98 | onPrint() { 99 | this.onDataRender(); 100 | 101 | const { data, header } = this.raw; 102 | const table = ExportMethod.print(data, header); 103 | 104 | Utilities.print(table); 105 | } 106 | 107 | checkHeader() { 108 | const { columns } = this.state; 109 | if (columns.length !== this.raw.header.length) { 110 | this.raw.header = []; 111 | columns.forEach(element => { 112 | if (element.export !== false) { 113 | this.raw.header.push(element); 114 | } 115 | }); 116 | } 117 | } 118 | 119 | render() { 120 | const { dropdown, columns, data } = this.state; 121 | const { filter, print, children, filterPlaceholder } = this.props; 122 | return ( 123 | <> 124 |
125 | {filter && this.onFilter(e.target.value)} placeholder={filterPlaceholder} />} 126 |
127 | {/* eslint-disable-next-line react/destructuring-assignment */} 128 | {this.props.export && ( 129 | this.setState(prevState => ({ dropdown: !prevState.dropdown }))} 132 | onClick={(e, type) => this.onExport(e, type)} 133 | /> 134 | )} 135 | {print && this.onPrint()} />} 136 |
137 |
138 | {React.cloneElement(children, { columns, data })} 139 | 140 | 141 | ); 142 | } 143 | } 144 | 145 | DataTableExtensions.propTypes = { 146 | columns: PropTypes.array, 147 | data: PropTypes.array, 148 | filter: PropTypes.bool, 149 | filterPlaceholder: PropTypes.string, 150 | export: PropTypes.bool, 151 | print: PropTypes.bool, 152 | exportHeaders: PropTypes.bool, 153 | children: PropTypes.node, 154 | filterHidden: PropTypes.bool, 155 | filterDigit: PropTypes.number, 156 | fileName: PropTypes.string, 157 | }; 158 | 159 | DataTableExtensions.defaultProps = { 160 | columns: [], 161 | data: [], 162 | filter: true, 163 | export: true, 164 | print: true, 165 | exportHeaders: false, 166 | children: null, 167 | filterHidden: true, 168 | filterPlaceholder: 'Filter Table', 169 | filterDigit: 2, 170 | fileName: document.title, 171 | }; 172 | 173 | export default DataTableExtensions; 174 | -------------------------------------------------------------------------------- /src/ui.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export const Filter = ({ onChange, placeholder }) => ( 5 |
6 | {/* eslint-disable-next-line jsx-a11y/control-has-associated-label */} 7 |
19 | ); 20 | 21 | Filter.propTypes = { 22 | onChange: PropTypes.func, 23 | placeholder: PropTypes.string.isRequired, 24 | }; 25 | 26 | Filter.defaultProps = { 27 | onChange: null, 28 | }; 29 | 30 | export const Export = props => { 31 | const { className, onDropdown, onClick } = props; 32 | return ( 33 | <> 34 | {/* eslint-disable-next-line jsx-a11y/control-has-associated-label */} 35 | 42 | 43 | 44 | 45 | ); 46 | }; 47 | 48 | Export.propTypes = { 49 | className: PropTypes.string, 50 | onDropdown: PropTypes.func, 51 | onClick: PropTypes.func, 52 | }; 53 | 54 | Export.defaultProps = { 55 | className: '', 56 | onDropdown: null, 57 | onClick: null, 58 | }; 59 | 60 | export const Print = props => ( 61 | // eslint-disable-next-line jsx-a11y/control-has-associated-label 62 |