├── .babelrc.json ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md └── stale.yml ├── .gitignore ├── .npmignore ├── .nvmrc ├── .prettierrc.js ├── .storybook ├── base.css ├── main.ts ├── manager.js └── preview.ts ├── .stylelintrc ├── .travis.yml ├── .vscode ├── launch.json └── settings.json ├── .yarn ├── install-state.gz └── releases │ └── yarn-4.0.2.cjs ├── .yarnrc.yml ├── CODE-OF-CONDUCT.md ├── LICENSE ├── README.md ├── __mocks__ ├── file.mock.js └── style.mock.js ├── eslintrc-js.js ├── jest.config.js ├── netlify.toml ├── package.json ├── rollup ├── rollup.config.cjs.js ├── rollup.config.common.js ├── rollup.config.dev.js ├── rollup.config.es.js └── rollup.config.umd.js ├── src ├── DataTable │ ├── Cell.ts │ ├── Checkbox.tsx │ ├── ContextMenu.tsx │ ├── DataTable.tsx │ ├── ExpanderButton.tsx │ ├── ExpanderRow.tsx │ ├── NoDataWrapper.tsx │ ├── Pagination.tsx │ ├── ProgressWrapper.tsx │ ├── ResponsiveWrapper.tsx │ ├── Select.tsx │ ├── Table.tsx │ ├── TableBody.tsx │ ├── TableCell.tsx │ ├── TableCellCheckbox.tsx │ ├── TableCellExpander.tsx │ ├── TableCol.tsx │ ├── TableColCheckbox.tsx │ ├── TableColExpander.tsx │ ├── TableHead.tsx │ ├── TableHeadRow.tsx │ ├── TableHeader.tsx │ ├── TableRow.tsx │ ├── TableSubheader.tsx │ ├── TableWrapper.tsx │ ├── __tests__ │ │ ├── Checkbox.test.tsx │ │ ├── DataTable.test.tsx │ │ ├── Pagination.test.tsx │ │ ├── __snapshots__ │ │ │ ├── Checkbox.test.tsx.snap │ │ │ ├── DataTable.test.tsx.snap │ │ │ └── Pagination.test.tsx.snap │ │ ├── styles.test.ts │ │ ├── themes.test.ts │ │ └── util.test.ts │ ├── constants.ts │ ├── defaultProps.tsx │ ├── media.ts │ ├── styles.ts │ ├── tableReducer.ts │ ├── themes.ts │ ├── types.ts │ └── util.ts ├── hooks │ ├── useColumns.ts │ ├── useDidUpdateEffect.ts │ ├── useRTL.ts │ └── useWindowSize.ts ├── icons │ ├── Dropdown.tsx │ ├── ExpanderCollapsedIcon.tsx │ ├── ExpanderExpandedIcon.tsx │ ├── FirstPage.tsx │ ├── LastPage.tsx │ ├── Left.tsx │ ├── NativeSortIcon.tsx │ └── Right.tsx ├── index.ts └── internal │ └── test-helpers.tsx ├── stories ├── DataTable │ ├── CellConditionalStyling.stories.js │ ├── CellStyling.stories.js │ ├── ColumnReorder.stories.js │ ├── ColumnsHideMedia.stories.js │ ├── CustomCell.stories.js │ ├── CustomStylesGSheets.stories.js │ ├── CustomStylesGrid.stories.js │ ├── DelayedColumns.stories.js │ ├── ExportCSV.stories.js │ ├── Filtering.stories.js │ ├── KitchenSink.stories.js │ ├── OmitColumn.stories.js │ ├── OmitColumnDynamic.stories.js │ ├── OptimizedClass.stories.js │ ├── OptimizedHook.stories.js │ ├── Progess.stories.js │ ├── ProgessCustom.stories.js │ ├── ThemeCustom.stories.js │ ├── ThemeDark.stories.js │ ├── conditional │ │ └── rows.stories.js │ ├── expandable │ │ ├── basic.mdx │ │ ├── basic.stories.js │ │ ├── preDisabled.mdx │ │ ├── preDisabled.stories.js │ │ ├── preExpanded.mdx │ │ └── preExpanded.stories.js │ ├── material-ui │ │ ├── linearProgressBar.mdx │ │ ├── linearProgressBar.stories.js │ │ ├── pagination.mdx │ │ ├── pagination.stories.js │ │ ├── table.mdx │ │ └── table.stories.js │ ├── pagination │ │ ├── basic.mdx │ │ ├── basic.stories.js │ │ ├── options.mdx │ │ ├── options.stories.js │ │ ├── remote.mdx │ │ └── remote.stories.js │ ├── selectableRows │ │ ├── basic.mdx │ │ ├── basic.stories.js │ │ ├── preDisabled.mdx │ │ ├── preDisabled.stories.js │ │ ├── preSelected.mdx │ │ ├── preSelected.stories.js │ │ ├── rowMGMT.mdx │ │ └── rowMGMT.stories.js │ └── sorting │ │ ├── basic.mdx │ │ ├── basic.stories.js │ │ ├── remote.mdx │ │ ├── remote.stories.js │ │ ├── sortFunction.mdx │ │ ├── sortFunction.stories.js │ │ ├── sortFunctionCol.mdx │ │ └── sortFunctionCol.stories.js ├── coc.stories.mdx ├── columns.stories.mdx ├── conditionalFormatting.stories.mdx ├── constants │ ├── sampleDesserts.js │ ├── sampleMovieData.js │ └── sampleRMEpisodes.js ├── cssEscape.stories.mdx ├── customStyles.stories.mdx ├── development.stories.mdx ├── enums.stories.mdx ├── headers │ └── fixed.stories.js ├── installation.stories.mdx ├── intro.stories.mdx ├── issues.stories.mdx ├── libraryIntegration.stories.mdx ├── patterns.stories.mdx ├── performance.stories.mdx ├── props.stories.mdx ├── reporting.stories.mdx ├── shared │ ├── Button.js │ ├── CustomMaterialMenu.js │ └── users.js ├── simpleExamples.stories.mdx ├── theming.stories.mdx └── typescript.stories.mdx ├── tsconfig.json └── yarn.lock /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceType": "unambiguous", 3 | "presets": [ 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "targets": { 8 | "chrome": 100, 9 | "safari": 15, 10 | "firefox": 91 11 | } 12 | } 13 | ], 14 | "@babel/preset-typescript", 15 | "@babel/preset-react" 16 | ], 17 | "plugins": [] 18 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = tab 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | storybook-static 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | // Specifies the ESLint parser 4 | plugins: ['jest', '@typescript-eslint'], 5 | extends: [ 6 | 'plugin:react/recommended', 7 | 'plugin:@typescript-eslint/recommended', 8 | 'plugin:prettier/recommended', 9 | 'plugin:react-hooks/recommended', 10 | 'plugin:jsx-a11y/recommended', 11 | 'plugin:storybook/recommended', 12 | ], 13 | parserOptions: { 14 | ecmaVersion: 2018, 15 | // Allows for the parsing of modern ECMAScript features 16 | sourceType: 'module', 17 | // Allows for the use of imports 18 | ecmaFeatures: { 19 | jsx: true, // Allows for the parsing of JSX 20 | }, 21 | }, 22 | settings: { 23 | react: { 24 | version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use 25 | }, 26 | }, 27 | rules: { 28 | 'react/prop-types': 'off', 29 | '@typescript-eslint/no-unused-vars': [ 30 | 'error', 31 | { 32 | ignoreRestSiblings: true, 33 | }, 34 | ], 35 | // '@typescript-eslint/ban-ts-comment': [{ 'ts-ignore': 'allow-with-description', minimumDescriptionLength: 10 }], 36 | 'prettier/prettier': [ 37 | 'error', 38 | { 39 | singleQuote: true, 40 | }, 41 | ], // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 42 | // e.g. "@typescript-eslint/explicit-function-return-type": "off", 43 | }, 44 | overrides: [ 45 | { 46 | files: ['**/*.js', '**/*.jsx'], 47 | rules: { 48 | '@typescript-eslint/no-var-requires': 'off', 49 | '@typescript-eslint/explicit-module-boundary-types': 'off', 50 | 'react/display-name': 'off', 51 | }, 52 | }, 53 | ], 54 | }; 55 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=lf 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # github: [jbetancur] 4 | # patreon: # Replace with a single Patreon username 5 | open_collective: john-betancur 6 | # ko_fi: # Replace with a single Ko-fi username 7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | # liberapay: # Replace with a single Liberapay username 10 | # issuehunt: # Replace with a single IssueHunt username 11 | # otechie: # Replace with a single Otechie username 12 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[DESCRIPTION]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Issue Check list 11 | - [ ] Agree to the [Code of Conduct](https://github.com/jbetancur/react-data-table-component/blob/master/CODE-OF-CONDUCT.md) 12 | - [ ] Read the README 13 | - [ ] You are using React 16.8.0+ 14 | - [ ] You installed `styled-components` 15 | - [ ] Include relevant code or preferably a [code sandbox](https://codesandbox.io/embed/react-data-table-sandbox-ccyuu 16 | ) 17 | 18 | ## Describe the bug 19 | A clear and concise description of what the bug is. 20 | 21 | ## To Reproduce 22 | Steps to reproduce the behavior: 23 | 1. Go to '...' 24 | 2. Click on '....' 25 | 3. Scroll down to '....' 26 | 4. See error 27 | 28 | ## Expected behavior 29 | A clear and concise description of what you expected to happen. 30 | 31 | ## Code Sandbox, Screenshots, or Relevant Code 32 | Please include a codesandbox to help **expedite** troublshooting. 33 | 34 | https://codesandbox.io/embed/react-data-table-sandbox-ccyuu 35 | 36 | Otherwise, add screenshots and/or complete sample code to help explain your problem. 37 | 38 | ## Versions (please complete the following information) 39 | - React (RDT requires 16.8.0+) 40 | - Styled Components 41 | - OS: [e.g. iOS] 42 | - Browser [e.g. chrome, safari, firefox] 43 | 44 | ## Additional context 45 | Add any other context about the problem here. 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE]: [SHORT DESCRIPTION]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Feature Check list 11 | 12 | - [ ] Agree to the [Code of Conduct](https://github.com/jbetancur/react-data-table-component/blob/master/CODE-OF-CONDUCT.md) 13 | - [ ] Read the README to ensure the feature is not already present 14 | - [ ] You read [Creating Issues, Features and Pull Requests](https://github.com/jbetancur/react-data-table-component/issues/387) 15 | - [ ] Considered the value versus complexity for all users of the library as well as library maintenance 16 | - [ ] Considered if this can be a storybook or documentation example 17 | 18 | ## Is your feature request related to a problem? Please describe 19 | 20 | A clear and concise description of what the feature is. 21 | 22 | ## Describe the solution you'd like 23 | 24 | A clear and concise description of what you want to happen. 25 | 26 | ## Describe alternatives you've considered 27 | 28 | A clear and concise description of any alternative solutions or features you've considered. 29 | 30 | ## Additional context 31 | 32 | Add any other context or screenshots about the feature request here. 33 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 30 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - enhancement 10 | - feature 11 | # Label to use when marking an issue as stale 12 | staleLabel: wontfix 13 | # Comment to post when marking an issue as stale. Set to `false` to disable 14 | markComment: > 15 | This issue has been automatically marked as stale because it has not had 16 | recent activity. It will be closed if no further activity occurs. Thank you 17 | for your contributions. 18 | # Comment to post when closing a stale issue. Set to `false` to disable 19 | closeComment: false 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # dist 61 | dist/* 62 | 63 | # Storybook 64 | storybook-static 65 | 66 | # only support Yarn 67 | package-lock.json 68 | .DS_Store 69 | .vscode 70 | .idea 71 | 72 | # visualizer 73 | stats.html 74 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | __mocks__ 2 | .storybook 3 | .vscode 4 | coverage 5 | stories 6 | !dist 7 | .babelrc 8 | .eslintignore 9 | .eslintrc.json 10 | .travis.yml 11 | jsconfig.json 12 | rollup.config.*.js 13 | test-config.js 14 | yarn.lock 15 | *.log -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.10.0 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 120, 6 | tabWidth: 2, 7 | arrowParens: 'avoid', 8 | }; 9 | -------------------------------------------------------------------------------- /.storybook/base.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 4 | -webkit-font-smoothing: antialiased; 5 | } 6 | 7 | table { 8 | width: 100%; 9 | } 10 | 11 | code { 12 | line-height: unset !important; 13 | white-space: normal !important; 14 | } 15 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import remarkGfm from 'remark-gfm'; 2 | 3 | import type { StorybookConfig } from '@storybook/react-webpack5'; 4 | 5 | const config: StorybookConfig = { 6 | stories: ['../stories/**/*.stories.@(ts|tsx|js|jsx|mdx)'], 7 | 8 | typescript: { 9 | check: false, 10 | checkOptions: {}, 11 | reactDocgen: 'react-docgen-typescript', 12 | reactDocgenTypescriptOptions: { 13 | shouldExtractLiteralValuesFromEnum: true, 14 | propFilter: prop => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true), 15 | }, 16 | }, 17 | 18 | addons: [ 19 | { 20 | name: '@storybook/addon-docs', 21 | options: { 22 | mdxPluginOptions: { 23 | mdxCompileOptions: { 24 | remarkPlugins: [remarkGfm], 25 | }, 26 | }, 27 | }, 28 | }, 29 | { 30 | name: '@storybook/addon-storysource', 31 | options: { 32 | loaderOptions: { 33 | prettierConfig: { printWidth: 80, singleQuote: true }, 34 | }, 35 | }, 36 | }, 37 | { 38 | name: '@storybook/addon-essentials', 39 | options: {}, 40 | }, 41 | { 42 | name: '@storybook/addon-a11y', 43 | options: { 44 | runOnly: { 45 | type: 'tag', 46 | values: ['wcag2a', 'wcag2aa'], 47 | }, 48 | }, 49 | }, 50 | ], 51 | 52 | docs: { 53 | autodocs: true, 54 | }, 55 | 56 | framework: { 57 | name: '@storybook/react-webpack5', 58 | options: {}, 59 | }, 60 | }; 61 | 62 | export default config; 63 | -------------------------------------------------------------------------------- /.storybook/manager.js: -------------------------------------------------------------------------------- 1 | import { create } from '@storybook/theming'; 2 | import { addons } from '@storybook/addons'; 3 | 4 | addons.setConfig({ 5 | isFullscreen: false, 6 | showAddonsPanel: true, 7 | panelPosition: 'bottom', 8 | theme: create({ 9 | base: 'light', 10 | brandTitle: 'React Data Table Component', 11 | brandUrl: 'https://github.com/jbetancur/react-data-table-component', 12 | gridCellSize: 12, 13 | }), 14 | }); 15 | -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | 3 | export const parameters = { 4 | // controls: { expanded: true }, 5 | viewMode: 'docs', 6 | options: { 7 | storySort: { 8 | method: 'alphabetical', 9 | order: [ 10 | 'Getting Started', 11 | [ 12 | 'Introduction', 13 | 'Installation', 14 | 'Basic Examples', 15 | 'Patterns', 16 | 'Kitchen Sink', 17 | 'Kitchen Sink TS', 18 | 'Code of Conduct', 19 | 'Features & Issues', 20 | '*', 21 | ], 22 | 'API Reference', 23 | ['Columns', 'Properties'], 24 | 'Columns', 25 | 'Sorting', 26 | 'Selectable', 27 | 'Expandable', 28 | 'Pagination', 29 | 'Headers', 30 | 'Loading', 31 | '*', 32 | 'Performance', 33 | ['Optimization', '*'], 34 | 'Contributing', 35 | ], 36 | }, 37 | }, 38 | a11y: { 39 | element: '#root', 40 | config: {}, 41 | options: {}, 42 | manual: true, 43 | }, 44 | }; 45 | 46 | export const globalTypes = { 47 | theme: { 48 | name: 'Theme', 49 | description: 'Global theme for components', 50 | defaultValue: 'light', 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["stylelint-prettier"], 3 | "extends": [ 4 | "stylelint-config-standard" 5 | ], 6 | "rules": { 7 | "prettier/prettier": true 8 | }, 9 | "customSyntax": "postcss-styled-syntax" 10 | } 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - 14 7 | 8 | cache: 9 | yarn: true 10 | 11 | script: 12 | - yarn lint 13 | - yarn test 14 | - yarn build 15 | 16 | before_deploy: 17 | - yarn build-storybook 18 | 19 | deploy: 20 | strategy: git 21 | provider: pages 22 | skip_cleanup: true 23 | token: $GITHUB_TOKEN # Set in the settings page of your repository, as a secure variable 24 | local_dir: storybook-static 25 | on: 26 | branch: next 27 | after_script: 28 | - yarn codecov 29 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Tests", 6 | "type": "node", 7 | "request": "launch", 8 | "program": "${workspaceRoot}/node_modules/jest-cli/bin/jest.js", 9 | "stopOnEntry": false, 10 | "args": ["--runInBand"], 11 | "cwd": "${workspaceRoot}", 12 | "runtimeArgs": ["--nolazy"], 13 | "env": { 14 | "BABEL_ENV": "commonjs" 15 | }, 16 | "console": "integratedTerminal", 17 | "sourceMaps": true 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | ".editorconfig": "javascriptreact" 4 | }, 5 | "editor.formatOnType": true, 6 | // "eslint.options": {"configFile": "./package.json"}, 7 | "editor.tabSize": 2, 8 | "editor.trimAutoWhitespace": true, 9 | "files.trimTrailingWhitespace": true, 10 | "eslint.nodePath": "./src", 11 | "editor.formatOnSave": false, 12 | "window.zoomLevel": 0, 13 | "workbench.iconTheme": "material-icon-theme", 14 | // "terminal.integrated.fontFamily": "Knack Nerd Font", 15 | "terminal.integrated.fontSize": 13, 16 | "terminal.integrated.shellArgs.osx": [ 17 | "-l" 18 | ], 19 | "terminal.integrated.shell.osx": "zsh", 20 | "terminal.integrated.scrollback": 5000, 21 | "editor.fontSize": 13, 22 | // "editor.minimap.enabled": true, 23 | "extensions.autoUpdate": true, 24 | "editor.codeActionsOnSave": { 25 | "source.fixAll.eslint": "explicit" 26 | }, 27 | "[javascript]": { 28 | "editor.formatOnSave": true, 29 | "editor.defaultFormatter": "esbenp.prettier-vscode" 30 | }, 31 | "[typescript]": { 32 | "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" 33 | }, 34 | "[typescriptreact]": { 35 | "editor.defaultFormatter": "esbenp.prettier-vscode" 36 | }, 37 | "[jsonc]": { 38 | "editor.defaultFormatter": "vscode.json-language-features" 39 | }, 40 | "prettier.useTabs": true, 41 | } 42 | -------------------------------------------------------------------------------- /.yarn/install-state.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbetancur/react-data-table-component/48ab9d71d0eeb4ac890fde215dd1c588944ae661/.yarn/install-state.gz -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.0.2.cjs 4 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at johnnyazee@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Netlify Status](https://api.netlify.com/api/v1/badges/26e0d16d-a986-46b1-9097-1a76c10d7cad/deploy-status)](https://app.netlify.com/sites/react-data-table-component/deploys) [![npm version](https://badge.fury.io/js/react-data-table-component.svg)](https://badge.fury.io/js/react-data-table-component) [![codecov](https://codecov.io/gh/jbetancur/react-data-table-component/branch/master/graph/badge.svg)](https://codecov.io/gh/jbetancur/react-data-table-component) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 2 | 3 | # React Data Table Component 4 | 5 | [![GitHub release](https://img.shields.io/github/release/jbetancur/react-data-table-component.svg)](https://GitHub.com/jbetancur/react-data-table-component/releases/) 6 | 7 | Creating yet another React table library came out of necessity while developing a web application for a growing startup. I discovered that while there are some great table libraries out there, some required heavy customization, were missing out of the box features such as built in sorting and pagination, or required understanding the atomic structure of html tables. 8 | 9 | If you want to achieve balance with the force and want a simple but flexible table library give React Data Table Component a chance. If you require an Excel clone, then this is not the React table library you are looking for 👋 10 | 11 | # Key Features 12 | 13 | - Declarative configuration 14 | - Built-in and configurable: 15 | - Sorting 16 | - Selectable Rows 17 | - Expandable Rows 18 | - Pagination 19 | - Themeable/Customizable 20 | - Accessibility 21 | - Responsive (via x-scroll/flex) 22 | 23 | # Documentation Website 24 | 25 | [![Netlify Status](https://api.netlify.com/api/v1/badges/26e0d16d-a986-46b1-9097-1a76c10d7cad/deploy-status)](https://app.netlify.com/sites/react-data-table-component/deploys) 26 | 27 | The documentation contains information about installation, usage and contributions. 28 | 29 | https://react-data-table-component.netlify.app 30 | 31 | # Supporting React Data Table Component 32 | 33 | If you would like to support the project financially, visit 34 | [our campaign on OpenCollective](https://opencollective.com/react-data-table-component). Your contributions help accelerate the development of React Data Table Component! 35 | 36 | 37 | 38 | 39 | 40 | # Contributors 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /__mocks__/file.mock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /__mocks__/style.mock.js: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /eslintrc-js.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | sourceType: 'module', 4 | allowImportExportEverywhere: false, 5 | ecmaFeatures: { 6 | globalReturn: false, 7 | }, 8 | babelOptions: { 9 | configFile: './eslintrc-js.js', 10 | }, 11 | }, 12 | extends: ['plugin:jsx-a11y/recommended'], 13 | plugins: ['jest', 'react-hooks', 'jsx-a11y'], 14 | rules: { 15 | 'max-len': 0, 16 | 'react/forbid-prop-types': 0, 17 | 'import/no-extraneous-dependencies': 0, 18 | 'no-confusing-arrow': ['error', { allowParens: true }], 19 | 'arrow-parens': ['error', 'as-needed'], 20 | 'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }], 21 | 'import/prefer-default-export': 0, 22 | 'object-curly-newline': ['error', { consistent: true }], 23 | 'implicit-arrow-linebreak': 0, 24 | 'operator-linebreak': 0, 25 | 'linebreak-style': 0, 26 | 'arrow-body-style': 0, 27 | 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks 28 | 'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies 29 | 'react/jsx-props-no-spreading': 0, 30 | 'react/display-name': 0, 31 | }, 32 | env: { 33 | 'jest/globals': true, 34 | browser: true, 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | transform: { 4 | '^.+\\.(ts|tsx)$': 'ts-jest', 5 | }, 6 | testEnvironment: 'jsdom', 7 | collectCoverageFrom: [ 8 | 'src/**/*.{js,jsx,ts,tsx}', 9 | '!src/**/*.d.ts', 10 | '!src/**/internal/*', 11 | '!src/index.ts', 12 | '!src/**/(index|*.stories).js', 13 | '!src/DataTable/propTypes.js', 14 | ], 15 | coverageThreshold: { 16 | global: { 17 | branches: 80, 18 | functions: 80, 19 | lines: 80, 20 | statements: 80, 21 | }, 22 | }, 23 | coverageReporters: ['json', 'lcov', 'text', 'clover'], 24 | setupFiles: ['react-app-polyfill/jsdom'], 25 | setupFilesAfterEnv: ['jest-styled-components'], 26 | testMatch: ['/src/**/__tests__/**/*.{js,jsx,ts,tsx}', '/src/**/*.{spec,test}.{js,jsx,ts,tsx}'], 27 | watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'], 28 | testPathIgnorePatterns: ['/node_modules/', 'dist'], 29 | moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node'], 30 | moduleNameMapper: { 31 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 32 | '/__mocks__/file.mock.js', 33 | '\\.(css|less)$': '/__mocks__/style.mock.js', 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "storybook-static" 3 | command = "yarn lint && yarn test && yarn build && yarn build-storybook" 4 | [build.environment] 5 | NODE_VERSION = "20.11.0" 6 | YARN_VERSION = "4.0.2" 7 | DOTENV_DISPLAY_WARNING = "none" 8 | STORYBOOK_EXAMPLE_APP ="true" 9 | [[headers]] 10 | for = "/*" 11 | [headers.values] 12 | Access-Control-Allow-Origin = "*" 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-data-table-component", 3 | "version": "7.7.0", 4 | "description": "A simple to use declarative react based data table", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.es.js", 7 | "browser": "dist/index.cjs.js", 8 | "types": "dist/index.d.ts", 9 | "files": [ 10 | "dist" 11 | ], 12 | "keywords": [ 13 | "react", 14 | "data", 15 | "table", 16 | "tables", 17 | "react-table", 18 | "react-data-table", 19 | "react-data-table-component" 20 | ], 21 | "repository": "https://github.com/jbetancur/react-data-table-component", 22 | "author": "jbetancur", 23 | "license": "Apache-2.0", 24 | "prepublish": "tsc", 25 | "scripts": { 26 | "prepublishOnly": "npm run build", 27 | "build:dev": "rollup -c rollup/rollup.config.dev.js -m", 28 | "build:umd": "rollup -c rollup/rollup.config.umd.js", 29 | "build:cjs": "rollup -c rollup/rollup.config.cjs.js", 30 | "build:es": "rollup -c rollup/rollup.config.es.js", 31 | "build": "rimraf dist && npm run build:dev && npm run build:cjs && npm run build:umd && npm run build:es", 32 | "start": "npm run build:dev -- -w", 33 | "test": "jest --passWithNoTests --verbose --coverage", 34 | "test:tdd": "jest --watch", 35 | "test:tdd-coverage": "jest --watch --coverage", 36 | "test:coverage": "jest --coverage", 37 | "lint": "eslint . --ext .ts,.tsx && eslint . --ext .js --config eslintrc-js.js", 38 | "storybook": "storybook dev -p 6006", 39 | "build-storybook": "storybook build", 40 | "deploy-storybook": "gh-pages -d storybook-static" 41 | }, 42 | "devDependencies": { 43 | "@babel/core": "^7.26.9", 44 | "@babel/eslint-parser": "^7.26.8", 45 | "@babel/plugin-transform-class-properties": "^7.25.9", 46 | "@babel/preset-env": "^7.26.9", 47 | "@babel/preset-typescript": "^7.26.0", 48 | "@faker-js/faker": "^8.4.1", 49 | "@material-ui/core": "^4.12.4", 50 | "@material-ui/icons": "^4.11.3", 51 | "@rollup/plugin-commonjs": "^25.0.8", 52 | "@rollup/plugin-node-resolve": "^15.3.1", 53 | "@storybook/addon-a11y": "^7.6.20", 54 | "@storybook/addon-essentials": "^7.6.20", 55 | "@storybook/addon-storysource": "^7.6.20", 56 | "@storybook/react": "^7.6.20", 57 | "@storybook/react-webpack5": "^7.6.20", 58 | "@storybook/theming": "^7.6.20", 59 | "@testing-library/react": "^14.3.1", 60 | "@types/babel__preset-env": "^7.10.0", 61 | "@types/jest": "^29.5.14", 62 | "@types/lodash-es": "^4.17.12", 63 | "@types/lodash.orderby": "^4.6.9", 64 | "@types/node": "^20.17.19", 65 | "@types/react": "^18.3.18", 66 | "@types/react-dom": "^18.3.5", 67 | "@typescript-eslint/eslint-plugin": "^6.21.0", 68 | "@typescript-eslint/parser": "^6.21.0", 69 | "axios": "^1.7.9", 70 | "codecov": "^3.8.3", 71 | "eslint": "^8.57.1", 72 | "eslint-config-prettier": "^9.1.0", 73 | "eslint-plugin-import": "^2.31.0", 74 | "eslint-plugin-jest": "^27.9.0", 75 | "eslint-plugin-jsx-a11y": "^6.10.2", 76 | "eslint-plugin-prettier": "^5.2.3", 77 | "eslint-plugin-react": "^7.37.4", 78 | "eslint-plugin-react-hooks": "^4.6.2", 79 | "eslint-plugin-storybook": "^0.6.15", 80 | "gh-pages": "^6.3.0", 81 | "jest": "^29.7.0", 82 | "jest-environment-jsdom": "^29.7.0", 83 | "jest-styled-components": "^7.2.0", 84 | "jest-watch-typeahead": "^2.2.2", 85 | "lodash-es": "^4.17.21", 86 | "memoize-one": "^6.0.0", 87 | "moment": "^2.30.1", 88 | "postcss": "^8.5.3", 89 | "postcss-styled-syntax": "^0.6.4", 90 | "prettier": "^3.5.2", 91 | "react": "^18.3.1", 92 | "react-app-polyfill": "^3.0.0", 93 | "react-dom": "^18.3.1", 94 | "remark-gfm": "^3.0.1", 95 | "rimraf": "^5.0.10", 96 | "rollup": "^2.79.2", 97 | "rollup-plugin-terser": "^7.0.2", 98 | "rollup-plugin-typescript2": "^0.36.0", 99 | "rollup-plugin-visualizer": "^5.14.0", 100 | "storybook": "^7.6.20", 101 | "styled-components": "^6.1.15", 102 | "stylelint": "^16.14.1", 103 | "stylelint-config-standard": "^36.0.1", 104 | "stylelint-prettier": "^5.0.3", 105 | "ts-jest": "^29.2.6", 106 | "typescript": "^5.7.3" 107 | }, 108 | "dependencies": { 109 | "deepmerge": "^4.3.1" 110 | }, 111 | "peerDependencies": { 112 | "react": ">= 17.0.0", 113 | "styled-components": ">= 5.0.0" 114 | }, 115 | "peerDependenciesMeta": { 116 | "styled-components": { 117 | "optional": false 118 | } 119 | }, 120 | "packageManager": "yarn@4.6.0" 121 | } 122 | -------------------------------------------------------------------------------- /rollup/rollup.config.cjs.js: -------------------------------------------------------------------------------- 1 | import { terser } from 'rollup-plugin-terser'; 2 | import pkg from '../package.json'; 3 | 4 | import config, { plugins } from './rollup.config.common'; 5 | 6 | export default Object.assign(config, { 7 | output: [ 8 | { 9 | file: pkg.main, 10 | format: 'cjs', 11 | exports: 'named', 12 | sourcemap: true, 13 | }, 14 | ], 15 | plugins: plugins.concat([terser()]), 16 | }); 17 | -------------------------------------------------------------------------------- /rollup/rollup.config.common.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import visualizer from 'rollup-plugin-visualizer'; 4 | import typescript from 'rollup-plugin-typescript2'; 5 | 6 | export const plugins = [ 7 | resolve({ 8 | browser: true, 9 | preferBuiltins: true, 10 | extensions: ['.ts', '.tsx'], 11 | }), 12 | commonjs({ 13 | include: 'node_modules/**', 14 | }), 15 | visualizer(), 16 | typescript(), 17 | ]; 18 | 19 | export default { 20 | input: './src/index.ts', 21 | external: ['react', 'react-dom', 'styled-components'], 22 | }; 23 | -------------------------------------------------------------------------------- /rollup/rollup.config.dev.js: -------------------------------------------------------------------------------- 1 | import config, { plugins } from './rollup.config.common'; 2 | import pkg from '../package.json'; 3 | 4 | export default Object.assign(config, { 5 | output: { 6 | name: 'ReactDataTable', 7 | file: `dist/${pkg.name}.dev.js`, 8 | format: 'cjs', 9 | exports: 'named', 10 | }, 11 | plugins: plugins.concat([]), 12 | }); 13 | -------------------------------------------------------------------------------- /rollup/rollup.config.es.js: -------------------------------------------------------------------------------- 1 | import { terser } from 'rollup-plugin-terser'; 2 | import pkg from '../package.json'; 3 | 4 | import config, { plugins } from './rollup.config.common'; 5 | 6 | export default Object.assign(config, { 7 | output: [ 8 | { 9 | file: pkg.module, 10 | format: 'es', 11 | exports: 'named', 12 | }, 13 | ], 14 | plugins: plugins.concat([terser()]), 15 | }); 16 | -------------------------------------------------------------------------------- /rollup/rollup.config.umd.js: -------------------------------------------------------------------------------- 1 | import { terser } from 'rollup-plugin-terser'; 2 | import config, { plugins } from './rollup.config.common'; 3 | 4 | export default Object.assign(config, { 5 | output: [ 6 | { 7 | name: 'ReactDataTable', 8 | file: 'dist/react-data-table-component.umd.js', 9 | format: 'umd', 10 | globals: { 11 | react: 'React', 12 | 'react-dom': 'ReactDOM', 13 | 'styled-components': 'styled', 14 | 'lodash.orderby': 'orderby', 15 | deepmerge: 'deepmerge', 16 | }, 17 | exports: 'named', 18 | }, 19 | ], 20 | plugins: plugins.concat([terser()]), 21 | }); 22 | -------------------------------------------------------------------------------- /src/DataTable/Cell.ts: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | import { media } from './media'; 3 | import { TableColumnBase } from './types'; 4 | 5 | export const CellBase = styled.div<{ 6 | $headCell?: boolean; 7 | $noPadding?: boolean; 8 | }>` 9 | position: relative; 10 | display: flex; 11 | align-items: center; 12 | box-sizing: border-box; 13 | line-height: normal; 14 | ${({ theme, $headCell }) => theme[$headCell ? 'headCells' : 'cells'].style}; 15 | ${({ $noPadding }) => $noPadding && 'padding: 0'}; 16 | `; 17 | 18 | export type CellProps = Pick< 19 | TableColumnBase, 20 | 'button' | 'grow' | 'maxWidth' | 'minWidth' | 'width' | 'right' | 'center' | 'compact' | 'hide' | 'allowOverflow' 21 | >; 22 | 23 | // Flex calculations 24 | export const CellExtended = styled(CellBase)` 25 | flex-grow: ${({ button, grow }) => (grow === 0 || button ? 0 : grow || 1)}; 26 | flex-shrink: 0; 27 | flex-basis: 0; 28 | max-width: ${({ maxWidth }) => maxWidth || '100%'}; 29 | min-width: ${({ minWidth }) => minWidth || '100px'}; 30 | ${({ width }) => 31 | width && 32 | css` 33 | min-width: ${width}; 34 | max-width: ${width}; 35 | `}; 36 | ${({ right }) => right && 'justify-content: flex-end'}; 37 | ${({ button, center }) => (center || button) && 'justify-content: center'}; 38 | ${({ compact, button }) => (compact || button) && 'padding: 0'}; 39 | 40 | /* handle hiding cells */ 41 | ${({ hide }) => 42 | hide && 43 | hide === 'sm' && 44 | media.sm` 45 | display: none; 46 | `}; 47 | ${({ hide }) => 48 | hide && 49 | hide === 'md' && 50 | media.md` 51 | display: none; 52 | `}; 53 | ${({ hide }) => 54 | hide && 55 | hide === 'lg' && 56 | media.lg` 57 | display: none; 58 | `}; 59 | ${({ hide }) => 60 | hide && 61 | Number.isInteger(hide) && 62 | media.custom(hide as number)` 63 | display: none; 64 | `}; 65 | `; 66 | -------------------------------------------------------------------------------- /src/DataTable/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { handleFunctionProps, noop } from './util'; 3 | 4 | const defaultComponentName = 'input'; 5 | 6 | const calculateBaseStyle = (disabled: boolean) => ({ 7 | fontSize: '18px', 8 | ...(!disabled && { cursor: 'pointer' }), 9 | padding: 0, 10 | marginTop: '1px', 11 | verticalAlign: 'middle', 12 | position: 'relative', 13 | }); 14 | 15 | interface CheckboxProps { 16 | name: string; 17 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 18 | component?: any; 19 | componentOptions?: { [key: string]: unknown }; 20 | indeterminate?: boolean; 21 | checked?: boolean; 22 | disabled?: boolean; 23 | onClick?: (e: React.MouseEvent) => void; 24 | } 25 | 26 | function Checkbox({ 27 | name, 28 | component = defaultComponentName, 29 | componentOptions = { style: {} }, 30 | indeterminate = false, 31 | checked = false, 32 | disabled = false, 33 | onClick = noop, 34 | }: CheckboxProps): JSX.Element { 35 | const setCheckboxRef = (checkbox: HTMLInputElement) => { 36 | if (checkbox) { 37 | // eslint-disable-next-line no-param-reassign 38 | checkbox.indeterminate = indeterminate; 39 | } 40 | }; 41 | 42 | const TagName = component; 43 | const baseStyle = TagName !== defaultComponentName ? componentOptions.style : calculateBaseStyle(disabled); 44 | const resolvedComponentOptions = React.useMemo( 45 | () => handleFunctionProps(componentOptions, indeterminate), 46 | [componentOptions, indeterminate], 47 | ); 48 | 49 | return ( 50 | 63 | ); 64 | } 65 | 66 | export default React.memo(Checkbox); 67 | -------------------------------------------------------------------------------- /src/DataTable/ContextMenu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | import useRTL from '../hooks/useRTL'; 4 | import { Direction } from './constants'; 5 | import { ContextMessage } from './types'; 6 | 7 | const Title = styled.div` 8 | display: flex; 9 | align-items: center; 10 | flex: 1 0 auto; 11 | height: 100%; 12 | color: ${({ theme }) => theme.contextMenu.fontColor}; 13 | font-size: ${({ theme }) => theme.contextMenu.fontSize}; 14 | font-weight: 400; 15 | `; 16 | 17 | const ContextActions = styled.div` 18 | display: flex; 19 | align-items: center; 20 | justify-content: flex-end; 21 | flex-wrap: wrap; 22 | `; 23 | 24 | const ContextMenuStyle = styled.div<{ 25 | $rtl?: boolean; 26 | $visible: boolean; 27 | }>` 28 | position: absolute; 29 | top: 0; 30 | left: 0; 31 | width: 100%; 32 | height: 100%; 33 | box-sizing: inherit; 34 | z-index: 1; 35 | align-items: center; 36 | justify-content: space-between; 37 | display: flex; 38 | ${({ $rtl }) => $rtl && 'direction: rtl'}; 39 | ${({ theme }) => theme.contextMenu.style}; 40 | ${({ theme, $visible }) => $visible && theme.contextMenu.activeStyle}; 41 | `; 42 | 43 | const generateDefaultContextTitle = (contextMessage: ContextMessage, selectedCount: number, rtl: boolean) => { 44 | if (selectedCount === 0) { 45 | return null; 46 | } 47 | 48 | const datumName = selectedCount === 1 ? contextMessage.singular : contextMessage.plural; 49 | 50 | // TODO: add mock document rtl tests 51 | if (rtl) { 52 | return `${selectedCount} ${contextMessage.message || ''} ${datumName}`; 53 | } 54 | 55 | return `${selectedCount} ${datumName} ${contextMessage.message || ''}`; 56 | }; 57 | 58 | type ContextMenuProps = { 59 | contextMessage: ContextMessage; 60 | contextActions: React.ReactNode | React.ReactNode[]; 61 | contextComponent: React.ReactNode | null; 62 | selectedCount: number; 63 | direction: Direction; 64 | }; 65 | 66 | function ContextMenu({ 67 | contextMessage, 68 | contextActions, 69 | contextComponent, 70 | selectedCount, 71 | direction, 72 | }: ContextMenuProps): JSX.Element { 73 | const isRTL = useRTL(direction); 74 | const visible = selectedCount > 0; 75 | 76 | if (contextComponent) { 77 | return ( 78 | 79 | {React.cloneElement(contextComponent as React.ReactElement, { selectedCount })} 80 | 81 | ); 82 | } 83 | 84 | return ( 85 | 86 | {generateDefaultContextTitle(contextMessage, selectedCount, isRTL)} 87 | {contextActions} 88 | 89 | ); 90 | } 91 | 92 | export default ContextMenu; 93 | -------------------------------------------------------------------------------- /src/DataTable/ExpanderButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | import { ExpandableIcon } from './types'; 4 | 5 | const ButtonStyle = styled.button` 6 | display: inline-flex; 7 | align-items: center; 8 | user-select: none; 9 | white-space: nowrap; 10 | border: none; 11 | background-color: transparent; 12 | ${({ theme }) => theme.expanderButton.style}; 13 | `; 14 | 15 | type ExpanderButtonProps = { 16 | disabled?: boolean; 17 | expanded?: boolean; 18 | expandableIcon: ExpandableIcon; 19 | id: string | number; 20 | row: T; 21 | onToggled?: (row: T) => void; 22 | }; 23 | 24 | function ExpanderButton({ 25 | disabled = false, 26 | expanded = false, 27 | expandableIcon, 28 | id, 29 | row, 30 | onToggled, 31 | }: ExpanderButtonProps): JSX.Element { 32 | const icon = expanded ? expandableIcon.expanded : expandableIcon.collapsed; 33 | const handleToggle = () => onToggled && onToggled(row); 34 | 35 | return ( 36 | 45 | {icon} 46 | 47 | ); 48 | } 49 | 50 | export default ExpanderButton; 51 | -------------------------------------------------------------------------------- /src/DataTable/ExpanderRow.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled, { CSSObject } from 'styled-components'; 3 | import { ComponentProps, ExpandableRowsComponent } from './types'; 4 | 5 | const ExpanderRowStyle = styled.div<{ 6 | $extendedRowStyle: CSSObject; 7 | }>` 8 | width: 100%; 9 | box-sizing: border-box; 10 | ${({ theme }) => theme.expanderRow.style}; 11 | ${({ $extendedRowStyle }) => $extendedRowStyle}; 12 | `; 13 | 14 | type ExpanderRowProps = { 15 | data: T; 16 | ExpanderComponent: ExpandableRowsComponent; 17 | extendedRowStyle: CSSObject; 18 | extendedClassNames: string; 19 | expanderComponentProps: ComponentProps; 20 | }; 21 | 22 | function ExpanderRow({ 23 | data, 24 | ExpanderComponent, 25 | expanderComponentProps, 26 | extendedRowStyle, 27 | extendedClassNames, 28 | }: ExpanderRowProps): JSX.Element { 29 | // we need to strip of rdt_TableRow from extendedClassNames 30 | const classNamesSplit = extendedClassNames.split(' ').filter(c => c !== 'rdt_TableRow'); 31 | const classNames = ['rdt_ExpanderRow', ...classNamesSplit].join(' '); 32 | 33 | return ( 34 | 35 | 36 | 37 | ); 38 | } 39 | 40 | export default React.memo(ExpanderRow) as typeof ExpanderRow; 41 | -------------------------------------------------------------------------------- /src/DataTable/NoDataWrapper.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const NoDataWrapper = styled.div` 4 | box-sizing: border-box; 5 | width: 100%; 6 | height: 100%; 7 | ${({ theme }) => theme.noData.style}; 8 | `; 9 | 10 | export default NoDataWrapper; 11 | -------------------------------------------------------------------------------- /src/DataTable/ProgressWrapper.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const ProgressWrapper = styled.div` 4 | position: relative; 5 | box-sizing: border-box; 6 | width: 100%; 7 | height: 100%; 8 | ${props => props.theme.progress.style}; 9 | `; 10 | 11 | export default ProgressWrapper; 12 | -------------------------------------------------------------------------------- /src/DataTable/ResponsiveWrapper.tsx: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | 3 | /* Hack when using layovers/menus that get clipped by overflow-x 4 | when a table is responsive due to overflow-xy scroll spec stupidity. 5 | Note: The parent element height must be set to 100%! 6 | https://www.brunildo.org/test/Overflowxy2.html 7 | */ 8 | 9 | const ResponsiveWrapper = styled.div<{ 10 | $responsive: boolean; 11 | $fixedHeader?: boolean; 12 | $fixedHeaderScrollHeight?: string; 13 | }>` 14 | position: relative; 15 | width: 100%; 16 | border-radius: inherit; 17 | ${({ $responsive, $fixedHeader }) => 18 | $responsive && 19 | css` 20 | overflow-x: auto; 21 | 22 | // hidden prevents vertical scrolling in firefox when fixedHeader is disabled 23 | overflow-y: ${$fixedHeader ? 'auto' : 'hidden'}; 24 | min-height: 0; 25 | `}; 26 | 27 | ${({ $fixedHeader = false, $fixedHeaderScrollHeight = '100vh' }) => 28 | $fixedHeader && 29 | css` 30 | max-height: ${$fixedHeaderScrollHeight}; 31 | -webkit-overflow-scrolling: touch; 32 | `}; 33 | 34 | ${({ theme }) => theme.responsiveWrapper.style}; 35 | `; 36 | 37 | export default ResponsiveWrapper; 38 | -------------------------------------------------------------------------------- /src/DataTable/Select.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | import DropDownIcon from '../icons/Dropdown'; 4 | 5 | const SelectControl = styled.select` 6 | cursor: pointer; 7 | height: 24px; 8 | max-width: 100%; 9 | user-select: none; 10 | padding-left: 8px; 11 | padding-right: 24px; 12 | box-sizing: content-box; 13 | font-size: inherit; 14 | color: inherit; 15 | border: none; 16 | background-color: transparent; 17 | appearance: none; 18 | direction: ltr; 19 | flex-shrink: 0; 20 | 21 | &::-ms-expand { 22 | display: none; 23 | } 24 | 25 | &:disabled::-ms-expand { 26 | background: #f60; 27 | } 28 | 29 | option { 30 | color: initial; 31 | } 32 | `; 33 | 34 | const SelectWrapper = styled.div` 35 | position: relative; 36 | flex-shrink: 0; 37 | font-size: inherit; 38 | color: inherit; 39 | margin-top: 1px; 40 | 41 | svg { 42 | top: 0; 43 | right: 0; 44 | color: inherit; 45 | position: absolute; 46 | fill: currentColor; 47 | width: 24px; 48 | height: 24px; 49 | display: inline-block; 50 | user-select: none; 51 | pointer-events: none; 52 | } 53 | `; 54 | 55 | type SelectProps = { 56 | onChange: (e: React.ChangeEvent) => void; 57 | defaultValue: string | number; 58 | children: React.ReactNode; 59 | }; 60 | 61 | const Select = ({ defaultValue, onChange, ...rest }: SelectProps): JSX.Element => ( 62 | 63 | 64 | 65 | 66 | ); 67 | 68 | export default Select; 69 | -------------------------------------------------------------------------------- /src/DataTable/Table.tsx: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | 3 | const disabledCSS = css` 4 | pointer-events: none; 5 | opacity: 0.4; 6 | `; 7 | 8 | const TableStyle = styled.div<{ 9 | disabled?: boolean; 10 | }>` 11 | position: relative; 12 | box-sizing: border-box; 13 | display: flex; 14 | flex-direction: column; 15 | width: 100%; 16 | height: 100%; 17 | max-width: 100%; 18 | ${({ disabled }) => disabled && disabledCSS}; 19 | ${({ theme }) => theme.table.style}; 20 | `; 21 | 22 | export default TableStyle; 23 | -------------------------------------------------------------------------------- /src/DataTable/TableBody.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Body = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | `; 7 | 8 | export default Body; 9 | -------------------------------------------------------------------------------- /src/DataTable/TableCell.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled, { css, CSSObject } from 'styled-components'; 3 | import { CellExtended } from './Cell'; 4 | import { getProperty, getConditionalStyle } from './util'; 5 | import { TableColumn } from './types'; 6 | 7 | interface CellStyleProps { 8 | $renderAsCell: boolean | undefined; 9 | $wrapCell: boolean | undefined; 10 | $allowOverflow: boolean | undefined; 11 | $cellStyle: CSSObject | undefined; 12 | $isDragging: boolean; 13 | } 14 | 15 | const overflowCSS = css` 16 | div:first-child { 17 | white-space: ${({ $wrapCell }) => ($wrapCell ? 'normal' : 'nowrap')}; 18 | overflow: ${({ $allowOverflow }) => ($allowOverflow ? 'visible' : 'hidden')}; 19 | text-overflow: ellipsis; 20 | } 21 | `; 22 | 23 | const CellStyle = styled(CellExtended).attrs(props => ({ 24 | style: props.style, 25 | }))` 26 | ${({ $renderAsCell }) => !$renderAsCell && overflowCSS}; 27 | ${({ theme, $isDragging }) => $isDragging && theme.cells.draggingStyle}; 28 | ${({ $cellStyle }) => $cellStyle}; 29 | `; 30 | 31 | interface CellProps { 32 | id: string; 33 | dataTag: string | null; 34 | column: TableColumn; 35 | row: T; 36 | rowIndex: number; 37 | isDragging: boolean; 38 | onDragStart: (e: React.DragEvent) => void; 39 | onDragOver: (e: React.DragEvent) => void; 40 | onDragEnd: (e: React.DragEvent) => void; 41 | onDragEnter: (e: React.DragEvent) => void; 42 | onDragLeave: (e: React.DragEvent) => void; 43 | } 44 | 45 | function Cell({ 46 | id, 47 | column, 48 | row, 49 | rowIndex, 50 | dataTag, 51 | isDragging, 52 | onDragStart, 53 | onDragOver, 54 | onDragEnd, 55 | onDragEnter, 56 | onDragLeave, 57 | }: CellProps): JSX.Element { 58 | const { conditionalStyle, classNames } = getConditionalStyle(row, column.conditionalCellStyles, ['rdt_TableCell']); 59 | 60 | return ( 61 | 88 | {!column.cell &&
{getProperty(row, column.selector, column.format, rowIndex)}
} 89 | {column.cell && column.cell(row, rowIndex, column, id)} 90 |
91 | ); 92 | } 93 | 94 | export default React.memo(Cell) as typeof Cell; 95 | -------------------------------------------------------------------------------- /src/DataTable/TableCellCheckbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | import { CellBase } from './Cell'; 4 | import Checkbox from './Checkbox'; 5 | import { RowState, SingleRowAction, ComponentProps } from './types'; 6 | 7 | const TableCellCheckboxStyle = styled(CellBase)` 8 | flex: 0 0 48px; 9 | min-width: 48px; 10 | justify-content: center; 11 | align-items: center; 12 | user-select: none; 13 | white-space: nowrap; 14 | `; 15 | 16 | type TableCellCheckboxProps = { 17 | name: string; 18 | keyField: string; 19 | row: T; 20 | rowCount: number; 21 | selected: boolean; 22 | selectableRowsComponent: 'input' | React.ReactNode; 23 | selectableRowsComponentProps: ComponentProps; 24 | selectableRowsSingle: boolean; 25 | selectableRowDisabled: RowState; 26 | onSelectedRow: (action: SingleRowAction) => void; 27 | }; 28 | 29 | function TableCellCheckbox({ 30 | name, 31 | keyField, 32 | row, 33 | rowCount, 34 | selected, 35 | selectableRowsComponent, 36 | selectableRowsComponentProps, 37 | selectableRowsSingle, 38 | selectableRowDisabled, 39 | onSelectedRow, 40 | }: TableCellCheckboxProps): JSX.Element { 41 | const disabled = !!(selectableRowDisabled && selectableRowDisabled(row)); 42 | 43 | const handleOnRowSelected = () => { 44 | onSelectedRow({ 45 | type: 'SELECT_SINGLE_ROW', 46 | row, 47 | isSelected: selected, 48 | keyField, 49 | rowCount, 50 | singleSelect: selectableRowsSingle, 51 | }); 52 | }; 53 | 54 | return ( 55 | e.stopPropagation()} className="rdt_TableCell" $noPadding> 56 | 65 | 66 | ); 67 | } 68 | 69 | export default TableCellCheckbox; 70 | -------------------------------------------------------------------------------- /src/DataTable/TableCellExpander.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | import { CellBase } from './Cell'; 4 | import ExpanderButton from './ExpanderButton'; 5 | import { ExpandableIcon } from './types'; 6 | 7 | const CellExpanderStyle = styled(CellBase)` 8 | white-space: nowrap; 9 | font-weight: 400; 10 | min-width: 48px; 11 | ${({ theme }) => theme.expanderCell.style}; 12 | `; 13 | 14 | type CellExpanderProps = { 15 | disabled: boolean; 16 | expanded: boolean; 17 | expandableIcon: ExpandableIcon; 18 | id: string | number; 19 | row: T; 20 | onToggled: (row: T) => void; 21 | }; 22 | 23 | function CellExpander({ 24 | row, 25 | expanded = false, 26 | expandableIcon, 27 | id, 28 | onToggled, 29 | disabled = false, 30 | }: CellExpanderProps): JSX.Element { 31 | return ( 32 | e.stopPropagation()} $noPadding> 33 | 41 | 42 | ); 43 | } 44 | 45 | export default CellExpander; 46 | -------------------------------------------------------------------------------- /src/DataTable/TableColCheckbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | import { CellBase } from './Cell'; 4 | import Checkbox from './Checkbox'; 5 | import { AllRowsAction, RowState } from './types'; 6 | 7 | const ColumnStyle = styled(CellBase)` 8 | flex: 0 0 48px; 9 | justify-content: center; 10 | align-items: center; 11 | user-select: none; 12 | white-space: nowrap; 13 | font-size: unset; 14 | `; 15 | 16 | interface ColumnCheckboxProps { 17 | headCell?: boolean; 18 | selectableRowsComponent: 'input' | React.ReactNode; 19 | selectableRowsComponentProps: Record; 20 | selectableRowDisabled: RowState; 21 | keyField: string; 22 | mergeSelections: boolean; 23 | rowData: T[]; 24 | selectedRows: T[]; 25 | allSelected: boolean; 26 | onSelectAllRows: (action: AllRowsAction) => void; 27 | } 28 | 29 | function ColumnCheckbox({ 30 | headCell = true, 31 | rowData, 32 | keyField, 33 | allSelected, 34 | mergeSelections, 35 | selectedRows, 36 | selectableRowsComponent, 37 | selectableRowsComponentProps, 38 | selectableRowDisabled, 39 | onSelectAllRows, 40 | }: ColumnCheckboxProps): JSX.Element { 41 | const indeterminate = selectedRows.length > 0 && !allSelected; 42 | const rows = selectableRowDisabled ? rowData.filter((row: T) => !selectableRowDisabled(row)) : rowData; 43 | const isDisabled = rows.length === 0; 44 | // The row count should subtract rows that are disabled 45 | const rowCount = Math.min(rowData.length, rows.length); 46 | 47 | const handleSelectAll = () => { 48 | onSelectAllRows({ 49 | type: 'SELECT_ALL_ROWS', 50 | rows, 51 | rowCount, 52 | mergeSelections, 53 | keyField, 54 | }); 55 | }; 56 | 57 | return ( 58 | 59 | 68 | 69 | ); 70 | } 71 | 72 | export default ColumnCheckbox; 73 | -------------------------------------------------------------------------------- /src/DataTable/TableColExpander.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { CellBase } from './Cell'; 3 | 4 | const ColumnExpander = styled(CellBase)` 5 | white-space: nowrap; 6 | ${({ theme }) => theme.expanderCell.style}; 7 | `; 8 | 9 | export default ColumnExpander; 10 | -------------------------------------------------------------------------------- /src/DataTable/TableHead.tsx: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | 3 | const fixedCSS = css` 4 | position: sticky; 5 | position: -webkit-sticky; /* Safari */ 6 | top: 0; 7 | z-index: 1; 8 | `; 9 | 10 | const Head = styled.div<{ 11 | $fixedHeader?: boolean; 12 | }>` 13 | display: flex; 14 | width: 100%; 15 | ${({ $fixedHeader }) => $fixedHeader && fixedCSS}; 16 | ${({ theme }) => theme.head.style}; 17 | `; 18 | 19 | export default Head; 20 | -------------------------------------------------------------------------------- /src/DataTable/TableHeadRow.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const HeadRow = styled.div<{ 4 | $dense?: boolean; 5 | disabled?: boolean; 6 | }>` 7 | display: flex; 8 | align-items: stretch; 9 | width: 100%; 10 | ${({ theme }) => theme.headRow.style}; 11 | ${({ $dense, theme }) => $dense && theme.headRow.denseStyle}; 12 | `; 13 | 14 | export default HeadRow; 15 | -------------------------------------------------------------------------------- /src/DataTable/TableHeader.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | import ContextMenu from './ContextMenu'; 4 | import { Direction } from './constants'; 5 | import { ContextMessage } from './types'; 6 | 7 | const HeaderStyle = styled.div` 8 | position: relative; 9 | box-sizing: border-box; 10 | overflow: hidden; 11 | display: flex; 12 | flex: 1 1 auto; 13 | align-items: center; 14 | justify-content: space-between; 15 | width: 100%; 16 | flex-wrap: wrap; 17 | ${({ theme }) => theme.header.style} 18 | `; 19 | 20 | const Title = styled.div` 21 | flex: 1 0 auto; 22 | color: ${({ theme }) => theme.header.fontColor}; 23 | font-size: ${({ theme }) => theme.header.fontSize}; 24 | font-weight: 400; 25 | `; 26 | 27 | const Actions = styled.div` 28 | flex: 1 0 auto; 29 | display: flex; 30 | align-items: center; 31 | justify-content: flex-end; 32 | 33 | > * { 34 | margin-left: 5px; 35 | } 36 | `; 37 | 38 | type HeaderProps = { 39 | title?: string | React.ReactNode; 40 | actions?: React.ReactNode | React.ReactNode[]; 41 | direction: Direction; 42 | selectedCount: number; 43 | showMenu?: boolean; 44 | contextMessage: ContextMessage; 45 | contextActions: React.ReactNode | React.ReactNode[]; 46 | contextComponent: React.ReactNode | null; 47 | }; 48 | 49 | const Header = ({ 50 | title, 51 | actions = null, 52 | contextMessage, 53 | contextActions, 54 | contextComponent, 55 | selectedCount, 56 | direction, 57 | showMenu = true, 58 | }: HeaderProps): JSX.Element => ( 59 | 60 | {title} 61 | {actions && {actions}} 62 | 63 | {showMenu && ( 64 | 71 | )} 72 | 73 | ); 74 | 75 | export default Header; 76 | -------------------------------------------------------------------------------- /src/DataTable/TableSubheader.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const alignMap = { 5 | left: 'flex-start', 6 | right: 'flex-end', 7 | center: 'center', 8 | }; 9 | 10 | type AlignItems = 'center' | 'left' | 'right'; 11 | 12 | const SubheaderWrapper = styled.header<{ 13 | align: AlignItems; 14 | $wrapContent: boolean; 15 | }>` 16 | position: relative; 17 | display: flex; 18 | flex: 1 1 auto; 19 | box-sizing: border-box; 20 | align-items: center; 21 | padding: 4px 16px 4px 24px; 22 | width: 100%; 23 | justify-content: ${({ align }) => alignMap[align]}; 24 | flex-wrap: ${({ $wrapContent }) => ($wrapContent ? 'wrap' : 'nowrap')}; 25 | ${({ theme }) => theme.subHeader.style} 26 | `; 27 | 28 | type SubheaderProps = { 29 | align?: AlignItems; 30 | wrapContent?: boolean; 31 | children?: React.ReactNode; 32 | }; 33 | 34 | const Subheader = ({ align = 'right', wrapContent = true, ...rest }: SubheaderProps): JSX.Element => ( 35 | 36 | ); 37 | 38 | export default Subheader; 39 | -------------------------------------------------------------------------------- /src/DataTable/TableWrapper.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Wrapper = styled.div` 4 | position: relative; 5 | width: 100%; 6 | ${({ theme }) => theme.tableWrapper.style}; 7 | `; 8 | 9 | export default Wrapper; 10 | -------------------------------------------------------------------------------- /src/DataTable/__tests__/Checkbox.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | import 'jest-styled-components'; 6 | import * as React from 'react'; 7 | import { render, fireEvent } from '@testing-library/react'; 8 | import Checkbox from '../Checkbox'; 9 | 10 | test('should render correctly when a custom component is not provided', () => { 11 | const { container } = render(); 12 | 13 | expect(container.firstChild).toMatchSnapshot(); 14 | }); 15 | 16 | test('component should render correctly with a custom checkbox', () => { 17 | // eslint-disable-next-line react/prefer-stateless-function 18 | class CustomComp extends React.Component { 19 | render() { 20 | return
25 schmeckles
; 21 | } 22 | } 23 | 24 | const { container } = render(); 25 | 26 | expect(container.firstChild).toMatchSnapshot(); 27 | }); 28 | 29 | test('component should render correctly with custom props', () => { 30 | const { container } = render(); 31 | 32 | expect(container.firstChild).toMatchSnapshot(); 33 | }); 34 | 35 | test('component should toggle indeterminate to true on the element', () => { 36 | const { container } = render(); 37 | const input = container.firstChild as HTMLInputElement; 38 | 39 | expect(input.indeterminate).toBe(true); 40 | }); 41 | 42 | test('component should not toggle indeterminate if there is no change', () => { 43 | const { container } = render(); 44 | const input = container.firstChild as HTMLInputElement; 45 | 46 | expect(input.indeterminate).toBe(false); 47 | }); 48 | 49 | test('should handle onClick', () => { 50 | const mockCallback = jest.fn(); 51 | const { container } = render(); 52 | const input = container.firstChild as HTMLInputElement; 53 | 54 | fireEvent.click(input); 55 | 56 | expect(mockCallback).toBeCalled(); 57 | }); 58 | -------------------------------------------------------------------------------- /src/DataTable/__tests__/__snapshots__/Checkbox.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`component should render correctly with a custom checkbox 1`] = ` 4 |
5 | 25 schmeckles 6 |
7 | `; 8 | 9 | exports[`component should render correctly with custom props 1`] = ` 10 | 17 | `; 18 | 19 | exports[`should render correctly when a custom component is not provided 1`] = ` 20 | 25 | `; 26 | -------------------------------------------------------------------------------- /src/DataTable/__tests__/styles.test.ts: -------------------------------------------------------------------------------- 1 | import { createStyles, defaultStyles } from '../styles'; 2 | import { defaultThemes } from '../themes'; 3 | 4 | describe('createStyles', () => { 5 | test('it should return the default styles if customStyles is not provided', () => { 6 | const newStyles = createStyles(); 7 | 8 | expect(newStyles).toEqual(defaultStyles(defaultThemes.default)); 9 | }); 10 | 11 | test('it should return the default styles if a non existent theme is not provided', () => { 12 | const newStyles = createStyles({}, 'poopyTheme'); 13 | 14 | expect(newStyles).toEqual(defaultStyles(defaultThemes.default)); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/DataTable/__tests__/themes.test.ts: -------------------------------------------------------------------------------- 1 | import { createTheme, defaultThemes } from '../themes'; 2 | 3 | describe('createTheme', () => { 4 | test('it should return the default theme if customTheme is not provided', () => { 5 | const newTheme = createTheme('whoa'); 6 | 7 | expect(newTheme).toEqual(defaultThemes.default); 8 | }); 9 | 10 | test('it should create a new theme that is composed with the default theme', () => { 11 | const newTheme = createTheme('wow', { 12 | text: { 13 | primary: '#', 14 | secondary: '#', 15 | disabled: '#', 16 | }, 17 | }); 18 | 19 | expect(defaultThemes.wow).toEqual(newTheme); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/DataTable/constants.ts: -------------------------------------------------------------------------------- 1 | export const STOP_PROP_TAG = 'allowRowEvents'; 2 | 3 | export enum Direction { 4 | LTR = 'ltr', 5 | RTL = 'rtl', 6 | AUTO = 'auto', 7 | } 8 | 9 | export enum Alignment { 10 | LEFT = 'left', 11 | RIGHT = 'right', 12 | CENTER = 'center', 13 | } 14 | 15 | export enum Media { 16 | SM = 'sm', 17 | MD = 'md', 18 | LG = 'lg', 19 | } 20 | -------------------------------------------------------------------------------- /src/DataTable/defaultProps.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import FirstPageIcon from '../icons/FirstPage'; 3 | import LastPageIcon from '../icons/LastPage'; 4 | import LeftIcon from '../icons/Left'; 5 | import RightIcon from '../icons/Right'; 6 | import ExpanderCollapsedIcon from '../icons/ExpanderCollapsedIcon'; 7 | import ExpanderExpandedIcon from '../icons/ExpanderExpandedIcon'; 8 | import { noop } from './util'; 9 | import { Alignment, Direction } from './constants'; 10 | 11 | export const defaultProps = { 12 | columns: [], 13 | data: [], 14 | title: '', 15 | keyField: 'id', 16 | selectableRows: false, 17 | selectableRowsHighlight: false, 18 | selectableRowsNoSelectAll: false, 19 | selectableRowSelected: null, 20 | selectableRowDisabled: null, 21 | selectableRowsComponent: 'input' as const, 22 | selectableRowsComponentProps: {}, 23 | selectableRowsVisibleOnly: false, 24 | selectableRowsSingle: false, 25 | clearSelectedRows: false, 26 | expandableRows: false, 27 | expandableRowDisabled: null, 28 | expandableRowExpanded: null, 29 | expandOnRowClicked: false, 30 | expandableRowsHideExpander: false, 31 | expandOnRowDoubleClicked: false, 32 | expandableInheritConditionalStyles: false, 33 | expandableRowsComponent: function DefaultExpander(): JSX.Element { 34 | return ( 35 |
36 | To add an expander pass in a component instance via expandableRowsComponent. You can then 37 | access props.data from this component. 38 |
39 | ); 40 | }, 41 | expandableIcon: { 42 | collapsed: , 43 | expanded: , 44 | }, 45 | expandableRowsComponentProps: {}, 46 | progressPending: false, 47 | progressComponent:
Loading...
, 48 | persistTableHead: false, 49 | sortIcon: null, 50 | sortFunction: null, 51 | sortServer: false, 52 | striped: false, 53 | highlightOnHover: false, 54 | pointerOnHover: false, 55 | noContextMenu: false, 56 | contextMessage: { singular: 'item', plural: 'items', message: 'selected' }, 57 | actions: null, 58 | contextActions: null, 59 | contextComponent: null, 60 | defaultSortFieldId: null, 61 | defaultSortAsc: true, 62 | responsive: true, 63 | noDataComponent:
There are no records to display
, 64 | disabled: false, 65 | noTableHead: false, 66 | noHeader: false, 67 | subHeader: false, 68 | subHeaderAlign: Alignment.RIGHT, 69 | subHeaderWrap: true, 70 | subHeaderComponent: null, 71 | fixedHeader: false, 72 | fixedHeaderScrollHeight: '100vh', 73 | pagination: false, 74 | paginationServer: false, 75 | paginationServerOptions: { 76 | persistSelectedOnSort: false, 77 | persistSelectedOnPageChange: false, 78 | }, 79 | paginationDefaultPage: 1, 80 | paginationResetDefaultPage: false, 81 | paginationTotalRows: 0, 82 | paginationPerPage: 10, 83 | paginationRowsPerPageOptions: [10, 15, 20, 25, 30], 84 | paginationComponent: null, 85 | paginationComponentOptions: {}, 86 | paginationIconFirstPage: , 87 | paginationIconLastPage: , 88 | paginationIconNext: , 89 | paginationIconPrevious: , 90 | dense: false, 91 | conditionalRowStyles: [], 92 | theme: 'default' as const, 93 | customStyles: {}, 94 | direction: Direction.AUTO, 95 | onChangePage: noop, 96 | onChangeRowsPerPage: noop, 97 | onRowClicked: noop, 98 | onRowDoubleClicked: noop, 99 | onRowMouseEnter: noop, 100 | onRowMouseLeave: noop, 101 | onRowExpandToggled: noop, 102 | onSelectedRowsChange: noop, 103 | onSort: noop, 104 | onColumnOrderChange: noop, 105 | }; 106 | -------------------------------------------------------------------------------- /src/DataTable/media.ts: -------------------------------------------------------------------------------- 1 | import { css, CSSObject, RuleSet } from 'styled-components'; 2 | 3 | export const SMALL = 599; 4 | export const MEDIUM = 959; 5 | export const LARGE = 1280; 6 | 7 | export const media = { 8 | sm: (literals: TemplateStringsArray, ...args: CSSObject[]): RuleSet => css` 9 | @media screen and (max-width: ${SMALL}px) { 10 | ${css(literals, ...args)} 11 | } 12 | `, 13 | md: (literals: TemplateStringsArray, ...args: CSSObject[]): RuleSet => css` 14 | @media screen and (max-width: ${MEDIUM}px) { 15 | ${css(literals, ...args)} 16 | } 17 | `, 18 | lg: (literals: TemplateStringsArray, ...args: CSSObject[]): RuleSet => css` 19 | @media screen and (max-width: ${LARGE}px) { 20 | ${css(literals, ...args)} 21 | } 22 | `, 23 | custom: 24 | (value: number) => 25 | (literals: TemplateStringsArray, ...args: CSSObject[]): RuleSet => css` 26 | @media screen and (max-width: ${value}px) { 27 | ${css(literals, ...args)} 28 | } 29 | `, 30 | }; 31 | -------------------------------------------------------------------------------- /src/DataTable/styles.ts: -------------------------------------------------------------------------------- 1 | import merge from 'deepmerge'; 2 | import { defaultThemes } from './themes'; 3 | import { TableStyles, Theme, Themes } from './types'; 4 | 5 | export const defaultStyles = (theme: Theme): TableStyles => ({ 6 | table: { 7 | style: { 8 | color: theme.text.primary, 9 | backgroundColor: theme.background.default, 10 | }, 11 | }, 12 | tableWrapper: { 13 | style: { 14 | display: 'table', 15 | }, 16 | }, 17 | responsiveWrapper: { 18 | style: {}, 19 | }, 20 | header: { 21 | style: { 22 | fontSize: '22px', 23 | color: theme.text.primary, 24 | backgroundColor: theme.background.default, 25 | minHeight: '56px', 26 | paddingLeft: '16px', 27 | paddingRight: '8px', 28 | }, 29 | }, 30 | subHeader: { 31 | style: { 32 | backgroundColor: theme.background.default, 33 | minHeight: '52px', 34 | }, 35 | }, 36 | head: { 37 | style: { 38 | color: theme.text.primary, 39 | fontSize: '12px', 40 | fontWeight: 500, 41 | }, 42 | }, 43 | headRow: { 44 | style: { 45 | backgroundColor: theme.background.default, 46 | minHeight: '52px', 47 | borderBottomWidth: '1px', 48 | borderBottomColor: theme.divider.default, 49 | borderBottomStyle: 'solid', 50 | }, 51 | denseStyle: { 52 | minHeight: '32px', 53 | }, 54 | }, 55 | headCells: { 56 | style: { 57 | paddingLeft: '16px', 58 | paddingRight: '16px', 59 | }, 60 | draggingStyle: { 61 | cursor: 'move', 62 | }, 63 | }, 64 | contextMenu: { 65 | style: { 66 | backgroundColor: theme.context.background, 67 | fontSize: '18px', 68 | fontWeight: 400, 69 | color: theme.context.text, 70 | paddingLeft: '16px', 71 | paddingRight: '8px', 72 | transform: 'translate3d(0, -100%, 0)', 73 | transitionDuration: '125ms', 74 | transitionTimingFunction: 'cubic-bezier(0, 0, 0.2, 1)', 75 | willChange: 'transform', 76 | }, 77 | activeStyle: { 78 | transform: 'translate3d(0, 0, 0)', 79 | }, 80 | }, 81 | cells: { 82 | style: { 83 | paddingLeft: '16px', 84 | paddingRight: '16px', 85 | wordBreak: 'break-word', 86 | }, 87 | draggingStyle: {}, 88 | }, 89 | rows: { 90 | style: { 91 | fontSize: '13px', 92 | fontWeight: 400, 93 | color: theme.text.primary, 94 | backgroundColor: theme.background.default, 95 | minHeight: '48px', 96 | '&:not(:last-of-type)': { 97 | borderBottomStyle: 'solid', 98 | borderBottomWidth: '1px', 99 | borderBottomColor: theme.divider.default, 100 | }, 101 | }, 102 | denseStyle: { 103 | minHeight: '32px', 104 | }, 105 | selectedHighlightStyle: { 106 | // use nth-of-type(n) to override other nth selectors 107 | '&:nth-of-type(n)': { 108 | color: theme.selected.text, 109 | backgroundColor: theme.selected.default, 110 | borderBottomColor: theme.background.default, 111 | }, 112 | }, 113 | highlightOnHoverStyle: { 114 | color: theme.highlightOnHover.text, 115 | backgroundColor: theme.highlightOnHover.default, 116 | transitionDuration: '0.15s', 117 | transitionProperty: 'background-color', 118 | borderBottomColor: theme.background.default, 119 | outlineStyle: 'solid', 120 | outlineWidth: '1px', 121 | outlineColor: theme.background.default, 122 | }, 123 | stripedStyle: { 124 | color: theme.striped.text, 125 | backgroundColor: theme.striped.default, 126 | }, 127 | }, 128 | expanderRow: { 129 | style: { 130 | color: theme.text.primary, 131 | backgroundColor: theme.background.default, 132 | }, 133 | }, 134 | expanderCell: { 135 | style: { 136 | flex: '0 0 48px', 137 | }, 138 | }, 139 | expanderButton: { 140 | style: { 141 | color: theme.button.default, 142 | fill: theme.button.default, 143 | backgroundColor: 'transparent', 144 | borderRadius: '2px', 145 | transition: '0.25s', 146 | height: '100%', 147 | width: '100%', 148 | '&:hover:enabled': { 149 | cursor: 'pointer', 150 | }, 151 | '&:disabled': { 152 | color: theme.button.disabled, 153 | }, 154 | '&:hover:not(:disabled)': { 155 | cursor: 'pointer', 156 | backgroundColor: theme.button.hover, 157 | }, 158 | '&:focus': { 159 | outline: 'none', 160 | backgroundColor: theme.button.focus, 161 | }, 162 | svg: { 163 | margin: 'auto', 164 | }, 165 | }, 166 | }, 167 | pagination: { 168 | style: { 169 | color: theme.text.secondary, 170 | fontSize: '13px', 171 | minHeight: '56px', 172 | backgroundColor: theme.background.default, 173 | borderTopStyle: 'solid', 174 | borderTopWidth: '1px', 175 | borderTopColor: theme.divider.default, 176 | }, 177 | pageButtonsStyle: { 178 | borderRadius: '50%', 179 | height: '40px', 180 | width: '40px', 181 | padding: '8px', 182 | margin: 'px', 183 | cursor: 'pointer', 184 | transition: '0.4s', 185 | color: theme.button.default, 186 | fill: theme.button.default, 187 | backgroundColor: 'transparent', 188 | '&:disabled': { 189 | cursor: 'unset', 190 | color: theme.button.disabled, 191 | fill: theme.button.disabled, 192 | }, 193 | '&:hover:not(:disabled)': { 194 | backgroundColor: theme.button.hover, 195 | }, 196 | '&:focus': { 197 | outline: 'none', 198 | backgroundColor: theme.button.focus, 199 | }, 200 | }, 201 | }, 202 | noData: { 203 | style: { 204 | display: 'flex', 205 | alignItems: 'center', 206 | justifyContent: 'center', 207 | color: theme.text.primary, 208 | backgroundColor: theme.background.default, 209 | }, 210 | }, 211 | progress: { 212 | style: { 213 | display: 'flex', 214 | alignItems: 'center', 215 | justifyContent: 'center', 216 | color: theme.text.primary, 217 | backgroundColor: theme.background.default, 218 | }, 219 | }, 220 | }); 221 | 222 | export const createStyles = ( 223 | customStyles: TableStyles = {}, 224 | themeName = 'default', 225 | inherit: Themes = 'default', 226 | ): TableStyles => { 227 | const themeType = defaultThemes[themeName] ? themeName : inherit; 228 | 229 | return merge(defaultStyles(defaultThemes[themeType]), customStyles); 230 | }; 231 | -------------------------------------------------------------------------------- /src/DataTable/tableReducer.ts: -------------------------------------------------------------------------------- 1 | import { insertItem, isRowSelected, removeItem } from './util'; 2 | import { Action, TableState } from './types'; 3 | 4 | export function tableReducer(state: TableState, action: Action): TableState { 5 | const toggleOnSelectedRowsChange = !state.toggleOnSelectedRowsChange; 6 | 7 | switch (action.type) { 8 | case 'SELECT_ALL_ROWS': { 9 | const { keyField, rows, rowCount, mergeSelections } = action; 10 | const allChecked = !state.allSelected; 11 | const toggleOnSelectedRowsChange = !state.toggleOnSelectedRowsChange; 12 | 13 | if (mergeSelections) { 14 | const selections = allChecked 15 | ? [...state.selectedRows, ...rows.filter(row => !isRowSelected(row, state.selectedRows, keyField))] 16 | : state.selectedRows.filter(row => !isRowSelected(row, rows, keyField)); 17 | 18 | return { 19 | ...state, 20 | allSelected: allChecked, 21 | selectedCount: selections.length, 22 | selectedRows: selections, 23 | toggleOnSelectedRowsChange, 24 | }; 25 | } 26 | 27 | return { 28 | ...state, 29 | allSelected: allChecked, 30 | selectedCount: allChecked ? rowCount : 0, 31 | selectedRows: allChecked ? rows : [], 32 | toggleOnSelectedRowsChange, 33 | }; 34 | } 35 | 36 | case 'SELECT_SINGLE_ROW': { 37 | const { keyField, row, isSelected, rowCount, singleSelect } = action; 38 | 39 | // handle single select mode 40 | if (singleSelect) { 41 | if (isSelected) { 42 | return { 43 | ...state, 44 | selectedCount: 0, 45 | allSelected: false, 46 | selectedRows: [], 47 | toggleOnSelectedRowsChange, 48 | }; 49 | } 50 | 51 | return { 52 | ...state, 53 | selectedCount: 1, 54 | allSelected: false, 55 | selectedRows: [row], 56 | toggleOnSelectedRowsChange, 57 | }; 58 | } 59 | 60 | // handle multi select mode 61 | if (isSelected) { 62 | return { 63 | ...state, 64 | selectedCount: state.selectedRows.length > 0 ? state.selectedRows.length - 1 : 0, 65 | allSelected: false, 66 | selectedRows: removeItem(state.selectedRows, row, keyField), 67 | toggleOnSelectedRowsChange, 68 | }; 69 | } 70 | 71 | return { 72 | ...state, 73 | selectedCount: state.selectedRows.length + 1, 74 | allSelected: state.selectedRows.length + 1 === rowCount, 75 | selectedRows: insertItem(state.selectedRows, row), 76 | toggleOnSelectedRowsChange, 77 | }; 78 | } 79 | 80 | case 'SELECT_MULTIPLE_ROWS': { 81 | const { keyField, selectedRows, totalRows, mergeSelections } = action; 82 | 83 | if (mergeSelections) { 84 | const selections = [ 85 | ...state.selectedRows, 86 | ...selectedRows.filter(row => !isRowSelected(row, state.selectedRows, keyField)), 87 | ]; 88 | 89 | return { 90 | ...state, 91 | selectedCount: selections.length, 92 | allSelected: false, 93 | selectedRows: selections, 94 | toggleOnSelectedRowsChange, 95 | }; 96 | } 97 | 98 | return { 99 | ...state, 100 | selectedCount: selectedRows.length, 101 | allSelected: selectedRows.length === totalRows, 102 | selectedRows, 103 | toggleOnSelectedRowsChange, 104 | }; 105 | } 106 | 107 | case 'CLEAR_SELECTED_ROWS': { 108 | const { selectedRowsFlag } = action; 109 | 110 | return { 111 | ...state, 112 | allSelected: false, 113 | selectedCount: 0, 114 | selectedRows: [], 115 | selectedRowsFlag, 116 | }; 117 | } 118 | 119 | case 'SORT_CHANGE': { 120 | const { sortDirection, selectedColumn, clearSelectedOnSort } = action; 121 | 122 | return { 123 | ...state, 124 | selectedColumn, 125 | sortDirection, 126 | currentPage: 1, 127 | // when using server-side paging reset selected row counts when sorting 128 | ...(clearSelectedOnSort && { 129 | allSelected: false, 130 | selectedCount: 0, 131 | selectedRows: [], 132 | toggleOnSelectedRowsChange, 133 | }), 134 | }; 135 | } 136 | 137 | case 'CHANGE_PAGE': { 138 | const { page, paginationServer, visibleOnly, persistSelectedOnPageChange } = action; 139 | const mergeSelections = paginationServer && persistSelectedOnPageChange; 140 | const clearSelectedOnPage = (paginationServer && !persistSelectedOnPageChange) || visibleOnly; 141 | 142 | return { 143 | ...state, 144 | currentPage: page, 145 | ...(mergeSelections && { 146 | allSelected: false, 147 | }), 148 | // when using server-side paging reset selected row counts 149 | ...(clearSelectedOnPage && { 150 | allSelected: false, 151 | selectedCount: 0, 152 | selectedRows: [], 153 | toggleOnSelectedRowsChange, 154 | }), 155 | }; 156 | } 157 | 158 | case 'CHANGE_ROWS_PER_PAGE': { 159 | const { rowsPerPage, page } = action; 160 | 161 | return { 162 | ...state, 163 | currentPage: page, 164 | rowsPerPage, 165 | }; 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/DataTable/themes.ts: -------------------------------------------------------------------------------- 1 | import merge from 'deepmerge'; 2 | import { Theme, Themes } from './types'; 3 | 4 | type ThemeMapping = { 5 | [propertyName: string]: Theme; 6 | }; 7 | 8 | const defaultTheme = { 9 | text: { 10 | primary: 'rgba(0, 0, 0, 0.87)', 11 | secondary: 'rgba(0, 0, 0, 0.54)', 12 | disabled: 'rgba(0, 0, 0, 0.38)', 13 | }, 14 | background: { 15 | default: '#FFFFFF', 16 | }, 17 | context: { 18 | background: '#e3f2fd', 19 | text: 'rgba(0, 0, 0, 0.87)', 20 | }, 21 | divider: { 22 | default: 'rgba(0,0,0,.12)', 23 | }, 24 | button: { 25 | default: 'rgba(0,0,0,.54)', 26 | focus: 'rgba(0,0,0,.12)', 27 | hover: 'rgba(0,0,0,.12)', 28 | disabled: 'rgba(0, 0, 0, .18)', 29 | }, 30 | selected: { 31 | default: '#e3f2fd', 32 | text: 'rgba(0, 0, 0, 0.87)', 33 | }, 34 | highlightOnHover: { 35 | default: '#EEEEEE', 36 | text: 'rgba(0, 0, 0, 0.87)', 37 | }, 38 | striped: { 39 | default: '#FAFAFA', 40 | text: 'rgba(0, 0, 0, 0.87)', 41 | }, 42 | }; 43 | 44 | export const defaultThemes: ThemeMapping = { 45 | default: defaultTheme, 46 | light: defaultTheme, 47 | dark: { 48 | text: { 49 | primary: '#FFFFFF', 50 | secondary: 'rgba(255, 255, 255, 0.7)', 51 | disabled: 'rgba(0,0,0,.12)', 52 | }, 53 | background: { 54 | default: '#424242', 55 | }, 56 | context: { 57 | background: '#E91E63', 58 | text: '#FFFFFF', 59 | }, 60 | divider: { 61 | default: 'rgba(81, 81, 81, 1)', 62 | }, 63 | button: { 64 | default: '#FFFFFF', 65 | focus: 'rgba(255, 255, 255, .54)', 66 | hover: 'rgba(255, 255, 255, .12)', 67 | disabled: 'rgba(255, 255, 255, .18)', 68 | }, 69 | selected: { 70 | default: 'rgba(0, 0, 0, .7)', 71 | text: '#FFFFFF', 72 | }, 73 | highlightOnHover: { 74 | default: 'rgba(0, 0, 0, .7)', 75 | text: '#FFFFFF', 76 | }, 77 | striped: { 78 | default: 'rgba(0, 0, 0, .87)', 79 | text: '#FFFFFF', 80 | }, 81 | }, 82 | }; 83 | 84 | export function createTheme(name = 'default', customTheme?: T, inherit: Themes = 'default'): Theme { 85 | if (!defaultThemes[name]) { 86 | defaultThemes[name] = merge(defaultThemes[inherit], customTheme || {}); 87 | } 88 | 89 | // allow tweaking default or light themes if the theme passed in matches 90 | defaultThemes[name] = merge(defaultThemes[name], customTheme || {}); 91 | 92 | return defaultThemes[name]; 93 | } 94 | -------------------------------------------------------------------------------- /src/hooks/useColumns.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { decorateColumns, findColumnIndexById, getSortDirection } from '../DataTable/util'; 3 | import useDidUpdateEffect from '../hooks/useDidUpdateEffect'; 4 | import { SortOrder, TableColumn } from '../DataTable/types'; 5 | 6 | type ColumnsHook = { 7 | tableColumns: TableColumn[]; 8 | draggingColumnId: string; 9 | handleDragStart: (e: React.DragEvent) => void; 10 | handleDragEnter: (e: React.DragEvent) => void; 11 | handleDragOver: (e: React.DragEvent) => void; 12 | handleDragLeave: (e: React.DragEvent) => void; 13 | handleDragEnd: (e: React.DragEvent) => void; 14 | defaultSortDirection: SortOrder; 15 | defaultSortColumn: TableColumn; 16 | }; 17 | 18 | function useColumns( 19 | columns: TableColumn[], 20 | onColumnOrderChange: (nextOrder: TableColumn[]) => void, 21 | defaultSortFieldId: string | number | null | undefined, 22 | defaultSortAsc: boolean, 23 | ): ColumnsHook { 24 | const [tableColumns, setTableColumns] = React.useState[]>(() => decorateColumns(columns)); 25 | const [draggingColumnId, setDraggingColumn] = React.useState(''); 26 | const sourceColumnId = React.useRef(''); 27 | 28 | useDidUpdateEffect(() => { 29 | setTableColumns(decorateColumns(columns)); 30 | }, [columns]); 31 | 32 | const handleDragStart = React.useCallback( 33 | (e: React.DragEvent) => { 34 | const { attributes } = e.target as HTMLDivElement; 35 | const id = attributes.getNamedItem('data-column-id')?.value; 36 | 37 | if (id) { 38 | sourceColumnId.current = tableColumns[findColumnIndexById(tableColumns, id)]?.id?.toString() || ''; 39 | 40 | setDraggingColumn(sourceColumnId.current); 41 | } 42 | }, 43 | [tableColumns], 44 | ); 45 | 46 | const handleDragEnter = React.useCallback( 47 | (e: React.DragEvent) => { 48 | const { attributes } = e.target as HTMLDivElement; 49 | const id = attributes.getNamedItem('data-column-id')?.value; 50 | 51 | if (id && sourceColumnId.current && id !== sourceColumnId.current) { 52 | const selectedColIndex = findColumnIndexById(tableColumns, sourceColumnId.current); 53 | const targetColIndex = findColumnIndexById(tableColumns, id); 54 | const reorderedCols = [...tableColumns]; 55 | 56 | reorderedCols[selectedColIndex] = tableColumns[targetColIndex]; 57 | reorderedCols[targetColIndex] = tableColumns[selectedColIndex]; 58 | 59 | setTableColumns(reorderedCols); 60 | 61 | onColumnOrderChange(reorderedCols); 62 | } 63 | }, 64 | [onColumnOrderChange, tableColumns], 65 | ); 66 | 67 | const handleDragOver = React.useCallback((e: React.DragEvent) => { 68 | e.preventDefault(); 69 | }, []); 70 | 71 | const handleDragLeave = React.useCallback((e: React.DragEvent) => { 72 | e.preventDefault(); 73 | }, []); 74 | 75 | const handleDragEnd = React.useCallback((e: React.DragEvent) => { 76 | e.preventDefault(); 77 | 78 | sourceColumnId.current = ''; 79 | 80 | setDraggingColumn(''); 81 | }, []); 82 | 83 | const defaultSortDirection = getSortDirection(defaultSortAsc); 84 | const defaultSortColumn = React.useMemo( 85 | () => tableColumns[findColumnIndexById(tableColumns, defaultSortFieldId?.toString())] || {}, 86 | [defaultSortFieldId, tableColumns], 87 | ); 88 | 89 | return { 90 | tableColumns, 91 | draggingColumnId, 92 | handleDragStart, 93 | handleDragEnter, 94 | handleDragOver, 95 | handleDragLeave, 96 | handleDragEnd, 97 | defaultSortDirection, 98 | defaultSortColumn, 99 | }; 100 | } 101 | 102 | export default useColumns; 103 | -------------------------------------------------------------------------------- /src/hooks/useDidUpdateEffect.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | type Hook = (fn: () => void, inputs: unknown[]) => void; 4 | 5 | const useFirstUpdate: Hook = (fn, inputs) => { 6 | const firstUpdate = React.useRef(true); 7 | 8 | React.useEffect(() => { 9 | if (firstUpdate.current) { 10 | firstUpdate.current = false; 11 | return; 12 | } 13 | 14 | fn(); 15 | // eslint-disable-next-line react-hooks/exhaustive-deps 16 | }, inputs); 17 | }; 18 | 19 | export default useFirstUpdate; 20 | -------------------------------------------------------------------------------- /src/hooks/useRTL.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Direction } from '../DataTable/constants'; 3 | 4 | function useRTL(direction: Direction = Direction.AUTO): boolean { 5 | const isClient = typeof window === 'object'; 6 | 7 | const [isRTL, setIsRTL] = React.useState(false); 8 | 9 | React.useEffect(() => { 10 | if (!isClient) { 11 | return; 12 | } 13 | 14 | if (direction === 'auto') { 15 | const canUse = !!(window.document && window.document.createElement); 16 | const bodyRTL = document.getElementsByTagName('BODY')[0]; 17 | const htmlTRL = document.getElementsByTagName('HTML')[0]; 18 | const hasRTL = bodyRTL.dir === 'rtl' || htmlTRL.dir === 'rtl'; 19 | 20 | setIsRTL(canUse && hasRTL); 21 | 22 | return; 23 | } 24 | 25 | setIsRTL(direction === 'rtl'); 26 | }, [direction, isClient]); 27 | 28 | return isRTL; 29 | } 30 | 31 | export default useRTL; 32 | -------------------------------------------------------------------------------- /src/hooks/useWindowSize.ts: -------------------------------------------------------------------------------- 1 | // Credit: https://usehooks.com/useWindowSize/ 2 | import * as React from 'react'; 3 | 4 | type Hook = () => { 5 | width: number | undefined; 6 | height: number | undefined; 7 | }; 8 | 9 | const useWindowSize: Hook = () => { 10 | const isClient = typeof window === 'object'; 11 | 12 | function getSize() { 13 | return { 14 | width: isClient ? window.innerWidth : undefined, 15 | height: isClient ? window.innerHeight : undefined, 16 | }; 17 | } 18 | 19 | const [windowSize, setWindowSize] = React.useState(getSize); 20 | 21 | React.useEffect(() => { 22 | if (!isClient) { 23 | return () => null; 24 | } 25 | 26 | function handleResize() { 27 | setWindowSize(getSize()); 28 | } 29 | 30 | window.addEventListener('resize', handleResize); 31 | return () => window.removeEventListener('resize', handleResize); 32 | // eslint-disable-next-line react-hooks/exhaustive-deps 33 | }, []); 34 | 35 | return windowSize; 36 | }; 37 | 38 | export default useWindowSize; 39 | -------------------------------------------------------------------------------- /src/icons/Dropdown.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const DropdownIcon: React.FC = () => ( 4 | 5 | 6 | 7 | 8 | ); 9 | 10 | export default DropdownIcon; 11 | -------------------------------------------------------------------------------- /src/icons/ExpanderCollapsedIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ExpanderCollapsedIcon: React.FC = () => ( 4 | 5 | 6 | 7 | 8 | ); 9 | 10 | export default ExpanderCollapsedIcon; 11 | -------------------------------------------------------------------------------- /src/icons/ExpanderExpandedIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ExpanderExpandedIcon: React.FC = () => ( 4 | 5 | 6 | 7 | 8 | ); 9 | 10 | export default ExpanderExpandedIcon; 11 | -------------------------------------------------------------------------------- /src/icons/FirstPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const FirstPage: React.FC = () => ( 4 | 15 | ); 16 | 17 | export default FirstPage; 18 | -------------------------------------------------------------------------------- /src/icons/LastPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const LastPage: React.FC = () => ( 4 | 15 | ); 16 | 17 | export default LastPage; 18 | -------------------------------------------------------------------------------- /src/icons/Left.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Left: React.FC = () => ( 4 | 15 | ); 16 | 17 | export default Left; 18 | -------------------------------------------------------------------------------- /src/icons/NativeSortIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import { SortOrder } from '../DataTable/types'; 4 | 5 | const Icon = styled.span<{ 6 | $sortActive: boolean; 7 | $sortDirection: SortOrder; 8 | }>` 9 | padding: 2px; 10 | color: inherit; 11 | flex-grow: 0; 12 | flex-shrink: 0; 13 | ${({ $sortActive }) => ($sortActive ? 'opacity: 1' : 'opacity: 0')}; 14 | ${({ $sortDirection }) => $sortDirection === 'desc' && 'transform: rotate(180deg)'}; 15 | `; 16 | 17 | interface NativeSortIconProps { 18 | sortActive: boolean; 19 | sortDirection: SortOrder; 20 | } 21 | 22 | const NativeSortIcon: React.FC = ({ sortActive, sortDirection }) => ( 23 | 24 | ▲ 25 | 26 | ); 27 | 28 | export default NativeSortIcon; 29 | -------------------------------------------------------------------------------- /src/icons/Right.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Right: React.FC = () => ( 4 | 15 | ); 16 | 17 | export default Right; 18 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import DataTable from './DataTable/DataTable'; 2 | 3 | export { defaultThemes, createTheme } from './DataTable/themes'; 4 | export * from './DataTable/constants'; 5 | export type { 6 | TableProps, 7 | TableProps as IDataTableProps, // this is for backwards compat with v6 8 | TableColumn, 9 | TableRow, 10 | TableStyles, 11 | Theme, 12 | Themes, 13 | ConditionalStyles, 14 | ExpanderComponentProps, 15 | PaginationComponentProps, 16 | PaginationOptions, 17 | PaginationServerOptions, 18 | ContextMessage, 19 | SortOrder, 20 | SortFunction, 21 | Selector, 22 | } from './DataTable/types'; 23 | 24 | export default DataTable; 25 | -------------------------------------------------------------------------------- /src/internal/test-helpers.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, RenderOptions, RenderResult } from '@testing-library/react'; 3 | import { ThemeProvider } from 'styled-components'; 4 | import 'jest-styled-components'; 5 | import { createStyles } from '../DataTable/styles'; 6 | 7 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 8 | export const renderWithTheme = (tree: React.ReactElement, ...args: RenderOptions[]): RenderResult => 9 | render({tree}, ...args); 10 | -------------------------------------------------------------------------------- /stories/DataTable/CellConditionalStyling.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import tableDataItems from '../constants/sampleDesserts'; 3 | import DataTable from '../../src/index'; 4 | 5 | const columns = [ 6 | { 7 | name: 'Name', 8 | selector: row => row.name, 9 | sortable: true, 10 | grow: 2, 11 | }, 12 | { 13 | name: 'Type', 14 | selector: row => row.type, 15 | sortable: true, 16 | }, 17 | { 18 | name: 'Calories (g)', 19 | selector: row => row.calories, 20 | sortable: true, 21 | right: true, 22 | conditionalCellStyles: [ 23 | { 24 | when: row => row.calories < 300, 25 | style: { 26 | backgroundColor: 'rgba(63, 195, 128, 0.9)', 27 | color: 'white', 28 | '&:hover': { 29 | cursor: 'pointer', 30 | }, 31 | }, 32 | }, 33 | { 34 | when: row => row.calories >= 300 && row.calories < 400, 35 | style: { 36 | backgroundColor: 'rgba(248, 148, 6, 0.9)', 37 | color: 'white', 38 | '&:hover': { 39 | cursor: 'pointer', 40 | }, 41 | }, 42 | }, 43 | { 44 | when: row => row.calories >= 400, 45 | style: { 46 | backgroundColor: 'rgba(242, 38, 19, 0.9)', 47 | color: 'white', 48 | '&:hover': { 49 | cursor: 'not-allowed', 50 | }, 51 | }, 52 | }, 53 | ], 54 | }, 55 | { 56 | name: 'Fat (g)', 57 | selector: row => row.fat, 58 | sortable: true, 59 | right: true, 60 | conditionalCellStyles: [ 61 | { 62 | when: row => row.fat <= 5, 63 | style: { 64 | backgroundColor: 'rgba(63, 195, 128, 0.9)', 65 | color: 'white', 66 | '&:hover': { 67 | cursor: 'pointer', 68 | }, 69 | }, 70 | }, 71 | { 72 | when: row => row.fat > 5 && row.fat < 10, 73 | style: { 74 | backgroundColor: 'rgba(248, 148, 6, 0.9)', 75 | color: 'white', 76 | '&:hover': { 77 | cursor: 'pointer', 78 | }, 79 | }, 80 | }, 81 | { 82 | when: row => row.fat > 10, 83 | style: { 84 | backgroundColor: 'rgba(242, 38, 19, 0.9)', 85 | color: 'white', 86 | '&:hover': { 87 | cursor: 'not-allowed', 88 | }, 89 | }, 90 | }, 91 | ], 92 | }, 93 | { 94 | name: 'Carbs (g)', 95 | selector: row => row.carbs, 96 | sortable: true, 97 | right: true, 98 | }, 99 | { 100 | name: 'Protein (g)', 101 | selector: row => row.protein, 102 | sortable: true, 103 | right: true, 104 | }, 105 | { 106 | name: 'Sodium (mg)', 107 | selector: row => row.sodium, 108 | sortable: true, 109 | right: true, 110 | }, 111 | { 112 | name: 'Calcium (%)', 113 | selector: row => row.calcium, 114 | sortable: true, 115 | right: true, 116 | }, 117 | { 118 | name: 'Iron (%)', 119 | selector: row => row.iron, 120 | sortable: true, 121 | right: true, 122 | }, 123 | ]; 124 | 125 | export const ConditionalStyling = () => ( 126 | 127 | ); 128 | 129 | export default { 130 | title: 'Columns/Cells/Conditional Styling', 131 | component: ConditionalStyling, 132 | }; 133 | -------------------------------------------------------------------------------- /stories/DataTable/CellStyling.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import tableDataItems from '../constants/sampleDesserts'; 3 | import DataTable from '../../src/index'; 4 | 5 | const columns = [ 6 | { 7 | name: 'Name', 8 | selector: row => row.name, 9 | sortable: true, 10 | grow: 2, 11 | style: { 12 | backgroundColor: 'rgba(63, 195, 128, 0.9)', 13 | color: 'white', 14 | }, 15 | }, 16 | { 17 | name: 'Type', 18 | selector: row => row.type, 19 | sortable: true, 20 | }, 21 | { 22 | name: 'Calories (g)', 23 | selector: row => row.calories, 24 | sortable: true, 25 | right: true, 26 | style: { 27 | backgroundColor: 'rgba(187, 204, 221, 1)', 28 | }, 29 | }, 30 | { 31 | name: 'Fat (g)', 32 | selector: row => row.fat, 33 | sortable: true, 34 | right: true, 35 | }, 36 | { 37 | name: 'Carbs (g)', 38 | selector: row => row.carbs, 39 | sortable: true, 40 | right: true, 41 | style: { 42 | backgroundColor: 'rgba(187, 204, 221, 1)', 43 | }, 44 | }, 45 | { 46 | name: 'Protein (g)', 47 | selector: row => row.protein, 48 | sortable: true, 49 | right: true, 50 | }, 51 | { 52 | name: 'Sodium (mg)', 53 | selector: row => row.sodium, 54 | sortable: true, 55 | right: true, 56 | style: { 57 | backgroundColor: 'rgba(187, 204, 221, 1)', 58 | }, 59 | }, 60 | { 61 | name: 'Calcium (%)', 62 | selector: row => row.calcium, 63 | sortable: true, 64 | right: true, 65 | }, 66 | { 67 | name: 'Iron (%)', 68 | selector: row => row.iron, 69 | sortable: true, 70 | right: true, 71 | style: { 72 | backgroundColor: 'rgba(187, 204, 221, 1)', 73 | }, 74 | }, 75 | ]; 76 | 77 | export const StaticStyling = () => ( 78 | 79 | ); 80 | 81 | export default { 82 | title: 'Columns/Cells/Static Styling', 83 | component: StaticStyling, 84 | }; 85 | -------------------------------------------------------------------------------- /stories/DataTable/ColumnReorder.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import data from '../constants/sampleDesserts'; 3 | import DataTable from '../../src/index'; 4 | 5 | const columns = [ 6 | { 7 | name: 'Name', 8 | selector: row => row.name, 9 | sortable: true, 10 | grow: 2, 11 | reorder: true, 12 | }, 13 | { 14 | name: 'Type', 15 | selector: row => row.type, 16 | sortable: true, 17 | reorder: true, 18 | }, 19 | { 20 | name: 'Calories (g)', 21 | selector: row => row.calories, 22 | sortable: true, 23 | right: true, 24 | reorder: true, 25 | }, 26 | { 27 | name: 'Fat (g)', 28 | selector: row => row.fat, 29 | sortable: true, 30 | right: true, 31 | reorder: true, 32 | }, 33 | { 34 | name: 'Carbs (g)', 35 | selector: row => row.carbs, 36 | sortable: true, 37 | right: true, 38 | reorder: true, 39 | }, 40 | { 41 | name: 'Protein (g)', 42 | selector: row => row.protein, 43 | sortable: true, 44 | right: true, 45 | reorder: true, 46 | }, 47 | { 48 | name: 'Sodium (mg)', 49 | selector: row => row.sodium, 50 | sortable: true, 51 | right: true, 52 | reorder: true, 53 | }, 54 | { 55 | name: 'Calcium (%)', 56 | selector: row => row.calcium, 57 | sortable: true, 58 | right: true, 59 | reorder: true, 60 | }, 61 | { 62 | name: 'Iron (%)', 63 | selector: row => row.iron, 64 | sortable: true, 65 | right: true, 66 | reorder: true, 67 | }, 68 | ]; 69 | 70 | export const Reorder = () => { 71 | return console.log(cols)} />; 72 | }; 73 | 74 | export default { 75 | title: 'Columns/Reorder', 76 | }; 77 | -------------------------------------------------------------------------------- /stories/DataTable/ColumnsHideMedia.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import data from '../constants/sampleDesserts'; 3 | import DataTable from '../../src/index'; 4 | 5 | const columns = [ 6 | { 7 | name: 'Name', 8 | selector: row => row.name, 9 | sortable: true, 10 | grow: 2, 11 | }, 12 | { 13 | name: 'Type', 14 | selector: row => row.type, 15 | sortable: true, 16 | hide: 'sm', 17 | }, 18 | { 19 | name: 'Calories (g)', 20 | selector: row => row.calories, 21 | sortable: true, 22 | right: true, 23 | }, 24 | { 25 | name: 'Fat (g)', 26 | selector: row => row.fat, 27 | sortable: true, 28 | right: true, 29 | hide: 'md', 30 | }, 31 | { 32 | name: 'Carbs (g)', 33 | selector: row => row.carbs, 34 | sortable: true, 35 | right: true, 36 | hide: 'md', 37 | }, 38 | { 39 | name: 'Protein (g)', 40 | selector: row => row.protein, 41 | sortable: true, 42 | right: true, 43 | hide: 'md', 44 | }, 45 | { 46 | name: 'Sodium (mg)', 47 | selector: row => row.sodium, 48 | sortable: true, 49 | right: true, 50 | hide: 'md', 51 | }, 52 | { 53 | name: 'Calcium (%)', 54 | selector: row => row.calcium, 55 | sortable: true, 56 | right: true, 57 | hide: 'md', 58 | }, 59 | { 60 | name: 'Iron (%)', 61 | selector: row => row.iron, 62 | sortable: true, 63 | right: true, 64 | hide: 'md', 65 | }, 66 | ]; 67 | export const HideOnResize = () => { 68 | // eslint-disable-next-line no-console 69 | const handleSort = (column, sortDirection) => console.log(column.selector, sortDirection); 70 | 71 | return ; 72 | }; 73 | 74 | export default { 75 | title: 'Columns/Hide On Resize', 76 | }; 77 | -------------------------------------------------------------------------------- /stories/DataTable/CustomCell.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import data from '../constants/sampleMovieData'; 3 | import DataTable from '../../src/index'; 4 | 5 | const Button = () => ; 6 | 7 | // eslint-disable-next-line react/prop-types 8 | const CustomTitle = ({ row }) => ( 9 |
10 | {/* eslint-disable-next-line react/prop-types */} 11 |
{row.title}
12 |
13 |
17 | {/* eslint-disable-next-line react/prop-types */} 18 | {row.plot} 19 |
20 |
21 |
22 | ); 23 | 24 | const columns = [ 25 | { 26 | name: 'Custom Title', 27 | selector: row => row.title, 28 | sortable: true, 29 | maxWidth: '600px', // when using custom you should use width or maxWidth, otherwise, the table will default to flex grow behavior 30 | cell: row => , 31 | }, 32 | { 33 | name: 'Plot Format', 34 | selector: row => row.plot, 35 | wrap: true, 36 | sortable: true, 37 | format: row => `${row.plot.slice(0, 200)}...`, 38 | }, 39 | { 40 | name: 'Genres', 41 | // eslint-disable-next-line react/no-array-index-key 42 | cell: row => ( 43 |
44 | {row.genres.map((genre, i) => ( 45 |
{genre}
46 | ))} 47 |
48 | ), 49 | }, 50 | { 51 | name: 'Thumbnail', 52 | grow: 0, 53 | cell: row => {row.name}, 54 | }, 55 | { 56 | name: 'Poster Link', 57 | button: true, 58 | cell: row => ( 59 | 60 | Download 61 | 62 | ), 63 | }, 64 | { 65 | name: 'Poster Button', 66 | button: true, 67 | cell: () => , 68 | }, 69 | ]; 70 | 71 | export const CustomCells = () => ( 72 | 73 | ); 74 | 75 | export default { 76 | title: 'Columns/Cells/Custom Cells', 77 | component: CustomCells, 78 | }; 79 | -------------------------------------------------------------------------------- /stories/DataTable/CustomStylesGSheets.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Icon from '@material-ui/icons/Apps'; 3 | import DataTable from '../../src/index'; 4 | import CustomMaterialMenu from '../shared/CustomMaterialMenu'; 5 | 6 | const data = [ 7 | { 8 | id: 1, 9 | title: 'Cutting Costs', 10 | by: 'me', 11 | lastOpened: 'Aug 7 9:52 AM', 12 | }, 13 | { 14 | id: 2, 15 | title: 'Wedding Planner', 16 | by: 'me', 17 | lastOpened: 'Sept 14 2:52 PM', 18 | }, 19 | { 20 | id: 3, 21 | title: 'Expense Tracker', 22 | by: 'me', 23 | lastOpened: 'Sept 12 2:41 PM', 24 | }, 25 | { 26 | id: 4, 27 | title: 'Home Brew Water Calculator', 28 | by: 'me', 29 | lastOpened: 'Jube 3 5:45 PM', 30 | }, 31 | ]; 32 | 33 | const customStyles = { 34 | headRow: { 35 | style: { 36 | border: 'none', 37 | }, 38 | }, 39 | headCells: { 40 | style: { 41 | color: '#202124', 42 | fontSize: '14px', 43 | }, 44 | }, 45 | rows: { 46 | highlightOnHoverStyle: { 47 | backgroundColor: 'rgb(230, 244, 244)', 48 | borderBottomColor: '#FFFFFF', 49 | borderRadius: '25px', 50 | outline: '1px solid #FFFFFF', 51 | }, 52 | }, 53 | pagination: { 54 | style: { 55 | border: 'none', 56 | }, 57 | }, 58 | }; 59 | 60 | const columns = [ 61 | { 62 | cell: () => , 63 | width: '56px', // custom width for icon button 64 | style: { 65 | borderBottom: '1px solid #FFFFFF', 66 | marginBottom: '-1px', 67 | }, 68 | }, 69 | { 70 | name: 'Title', 71 | selector: row => row.title, 72 | sortable: true, 73 | grow: 2, 74 | style: { 75 | color: '#202124', 76 | fontSize: '14px', 77 | fontWeight: 500, 78 | }, 79 | }, 80 | { 81 | name: 'Owner', 82 | selector: row => row.by, 83 | sortable: true, 84 | style: { 85 | color: 'rgba(0,0,0,.54)', 86 | }, 87 | }, 88 | { 89 | name: 'Last opened', 90 | selector: row => row.lastOpened, 91 | sortable: true, 92 | style: { 93 | color: 'rgba(0,0,0,.54)', 94 | }, 95 | }, 96 | { 97 | cell: row => , 98 | allowOverflow: true, 99 | button: true, 100 | width: '56px', 101 | }, 102 | ]; 103 | 104 | export const GoogleSheetsEsque = () => ( 105 | 113 | ); 114 | 115 | export default { 116 | title: 'Custom Styles/Google Sheets Esque', 117 | component: GoogleSheetsEsque, 118 | }; 119 | -------------------------------------------------------------------------------- /stories/DataTable/CustomStylesGrid.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import data from '../constants/sampleMovieData'; 3 | import DataTable, { defaultThemes } from '../../src/index'; 4 | 5 | const customStyles = { 6 | header: { 7 | style: { 8 | minHeight: '56px', 9 | }, 10 | }, 11 | headRow: { 12 | style: { 13 | borderTopStyle: 'solid', 14 | borderTopWidth: '1px', 15 | borderTopColor: defaultThemes.default.divider.default, 16 | }, 17 | }, 18 | headCells: { 19 | style: { 20 | '&:not(:last-of-type)': { 21 | borderRightStyle: 'solid', 22 | borderRightWidth: '1px', 23 | borderRightColor: defaultThemes.default.divider.default, 24 | }, 25 | }, 26 | }, 27 | cells: { 28 | style: { 29 | '&:not(:last-of-type)': { 30 | borderRightStyle: 'solid', 31 | borderRightWidth: '1px', 32 | borderRightColor: defaultThemes.default.divider.default, 33 | }, 34 | }, 35 | }, 36 | }; 37 | 38 | const columns = [ 39 | { 40 | name: 'Title', 41 | selector: row => row.title, 42 | sortable: true, 43 | }, 44 | { 45 | name: 'Director', 46 | selector: row => row.director, 47 | sortable: true, 48 | }, 49 | { 50 | name: 'Year', 51 | selector: row => row.year, 52 | sortable: true, 53 | }, 54 | ]; 55 | 56 | export const CompactGrid = () => ( 57 | 66 | ); 67 | 68 | export default { 69 | title: 'Custom Styles/Compact Grid', 70 | }; 71 | -------------------------------------------------------------------------------- /stories/DataTable/DelayedColumns.stories.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import users from '../shared/users'; 3 | import DataTable from '../../src/index'; 4 | 5 | export const Delayed = () => { 6 | const [columns, setColumns] = useState([]); 7 | const [pending, setPending] = React.useState(true); 8 | 9 | useEffect(() => { 10 | const timeout = setTimeout(() => { 11 | setColumns([ 12 | { 13 | name: 'Name', 14 | selector: row => row.name, 15 | sortable: true, 16 | }, 17 | { 18 | name: 'Email', 19 | selector: row => row.email, 20 | sortable: true, 21 | }, 22 | { 23 | name: 'Address', 24 | selector: row => row.address, 25 | sortable: true, 26 | }, 27 | ]); 28 | setPending(false); 29 | }, 2000); 30 | return () => clearTimeout(timeout); 31 | }, []); 32 | 33 | return ; 34 | }; 35 | 36 | export default { 37 | title: 'Columns/Delayed', 38 | Component: Delayed, 39 | }; 40 | -------------------------------------------------------------------------------- /stories/DataTable/ExportCSV.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from '../shared/Button'; 3 | import data from '../constants/sampleMovieData'; 4 | import DataTable from '../../src/index'; 5 | 6 | // Blatant "inspiration" from https://codepen.io/Jacqueline34/pen/pyVoWr 7 | function convertArrayOfObjectsToCSV(array) { 8 | let result; 9 | 10 | const columnDelimiter = ','; 11 | const lineDelimiter = '\n'; 12 | const keys = Object.keys(data[0]); 13 | 14 | result = ''; 15 | result += keys.join(columnDelimiter); 16 | result += lineDelimiter; 17 | 18 | array.forEach(item => { 19 | let ctr = 0; 20 | keys.forEach(key => { 21 | if (ctr > 0) result += columnDelimiter; 22 | 23 | result += item[key]; 24 | // eslint-disable-next-line no-plusplus 25 | ctr++; 26 | }); 27 | result += lineDelimiter; 28 | }); 29 | 30 | return result; 31 | } 32 | 33 | // Blatant "inspiration" from https://codepen.io/Jacqueline34/pen/pyVoWr 34 | function downloadCSV(array) { 35 | const link = document.createElement('a'); 36 | let csv = convertArrayOfObjectsToCSV(array); 37 | if (csv == null) return; 38 | 39 | const filename = 'export.csv'; 40 | 41 | if (!csv.match(/^data:text\/csv/i)) { 42 | csv = `data:text/csv;charset=utf-8,${csv}`; 43 | } 44 | 45 | link.setAttribute('href', encodeURI(csv)); 46 | link.setAttribute('download', filename); 47 | link.click(); 48 | } 49 | 50 | // eslint-disable-next-line react/prop-types 51 | const Export = ({ onExport }) => ; 52 | 53 | const columns = [ 54 | { 55 | name: 'Title', 56 | selector: row => row.title, 57 | sortable: true, 58 | }, 59 | { 60 | name: 'Director', 61 | selector: row => row.director, 62 | sortable: true, 63 | }, 64 | { 65 | name: 'Year', 66 | selector: row => row.year, 67 | sortable: true, 68 | }, 69 | ]; 70 | 71 | export const ExportCSV = () => { 72 | const actionsMemo = React.useMemo(() => downloadCSV(data)} />, []); 73 | 74 | return ; 75 | }; 76 | 77 | export default { 78 | title: 'Examples/Export CSV', 79 | component: ExportCSV, 80 | }; 81 | -------------------------------------------------------------------------------- /stories/DataTable/Filtering.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import { faker } from '@faker-js/faker'; 4 | import Button from '../shared/Button'; 5 | import DataTable from '../../src/index'; 6 | 7 | const createUser = () => ({ 8 | id: faker.string.uuid(), 9 | name: faker.internet.userName(), 10 | email: faker.internet.email(), 11 | address: faker.location.streetAddress(), 12 | bio: faker.lorem.sentence(), 13 | image: faker.image.avatar(), 14 | }); 15 | 16 | const createUsers = (numUsers = 5) => new Array(numUsers).fill(undefined).map(createUser); 17 | 18 | const fakeUsers = createUsers(2000); 19 | 20 | const TextField = styled.input` 21 | height: 32px; 22 | width: 200px; 23 | border-radius: 3px; 24 | border-top-left-radius: 5px; 25 | border-bottom-left-radius: 5px; 26 | border-top-right-radius: 0; 27 | border-bottom-right-radius: 0; 28 | border: 1px solid #e5e5e5; 29 | padding: 0 32px 0 16px; 30 | 31 | &:hover { 32 | cursor: pointer; 33 | } 34 | `; 35 | 36 | const ClearButton = styled(Button)` 37 | border-top-left-radius: 0; 38 | border-bottom-left-radius: 0; 39 | border-top-right-radius: 5px; 40 | border-bottom-right-radius: 5px; 41 | height: 34px; 42 | width: 32px; 43 | text-align: center; 44 | display: flex; 45 | align-items: center; 46 | justify-content: center; 47 | `; 48 | 49 | // eslint-disable-next-line react/prop-types 50 | const FilterComponent = ({ filterText, onFilter, onClear }) => ( 51 | <> 52 | 60 | 61 | X 62 | 63 | 64 | ); 65 | 66 | const columns = [ 67 | { 68 | name: 'Name', 69 | selector: row => row.name, 70 | sortable: true, 71 | }, 72 | { 73 | name: 'Email', 74 | selector: row => row.email, 75 | sortable: true, 76 | }, 77 | { 78 | name: 'Address', 79 | selector: row => row.address, 80 | sortable: true, 81 | }, 82 | ]; 83 | 84 | export const Filtering = () => { 85 | const [filterText, setFilterText] = React.useState(''); 86 | const [resetPaginationToggle, setResetPaginationToggle] = React.useState(false); 87 | const filteredItems = fakeUsers.filter( 88 | item => item.name && item.name.toLowerCase().includes(filterText.toLowerCase()), 89 | ); 90 | 91 | const subHeaderComponentMemo = React.useMemo(() => { 92 | const handleClear = () => { 93 | if (filterText) { 94 | setResetPaginationToggle(!resetPaginationToggle); 95 | setFilterText(''); 96 | } 97 | }; 98 | 99 | return ( 100 | setFilterText(e.target.value)} onClear={handleClear} filterText={filterText} /> 101 | ); 102 | }, [filterText, resetPaginationToggle]); 103 | 104 | return ( 105 | 116 | ); 117 | }; 118 | 119 | export default { 120 | title: 'Examples/Filtering', 121 | component: Filtering, 122 | }; 123 | -------------------------------------------------------------------------------- /stories/DataTable/OmitColumn.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import data from '../constants/sampleMovieData'; 3 | import DataTable from '../../src/index'; 4 | 5 | const columns = [ 6 | { 7 | name: 'Title', 8 | selector: row => row.title, 9 | sortable: true, 10 | }, 11 | { 12 | name: 'Director', 13 | selector: row => row.director, 14 | sortable: true, 15 | omit: true, 16 | }, 17 | { 18 | name: 'Year', 19 | selector: row => row.year, 20 | sortable: true, 21 | }, 22 | ]; 23 | 24 | export const Omit = () => ; 25 | 26 | export default { 27 | title: 'Columns/Omit', 28 | component: Omit, 29 | }; 30 | -------------------------------------------------------------------------------- /stories/DataTable/OmitColumnDynamic.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import data from '../constants/sampleMovieData'; 3 | import DataTable from '../../src/index'; 4 | import Button from '../shared/Button'; 5 | 6 | export const OmitDynamically = () => { 7 | const [hideDirector, setHideDirector] = React.useState(false); 8 | 9 | const columns = React.useMemo( 10 | () => [ 11 | { 12 | name: 'Title', 13 | selector: row => row.title, 14 | sortable: true, 15 | }, 16 | { 17 | name: 'Director', 18 | selector: row => row.director, 19 | sortable: true, 20 | omit: hideDirector, 21 | }, 22 | { 23 | name: 'Year', 24 | selector: row => row.year, 25 | sortable: true, 26 | }, 27 | ], 28 | [hideDirector], 29 | ); 30 | 31 | return ( 32 | <> 33 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | export default { 40 | title: 'Columns/Omit Dynamically', 41 | component: OmitDynamically, 42 | }; 43 | -------------------------------------------------------------------------------- /stories/DataTable/OptimizedClass.stories.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import memoize from 'memoize-one'; 3 | import tableDataItems from '../constants/sampleDesserts'; 4 | import DataTable from '../../src/index'; 5 | 6 | const columns = memoize(clickHandler => [ 7 | { 8 | // eslint-disable-next-line react/button-has-type 9 | cell: () => , 10 | ignoreRowClick: true, 11 | allowOverflow: true, 12 | button: true, 13 | }, 14 | { 15 | name: 'Name', 16 | selector: row => row.name, 17 | sortable: true, 18 | grow: 2, 19 | }, 20 | { 21 | name: 'Type', 22 | selector: row => row.type, 23 | sortable: true, 24 | }, 25 | { 26 | name: 'Calories (g)', 27 | selector: row => row.calories, 28 | sortable: true, 29 | right: true, 30 | }, 31 | { 32 | name: 'Fat (g)', 33 | selector: row => row.fat, 34 | sortable: true, 35 | right: true, 36 | }, 37 | { 38 | name: 'Carbs (g)', 39 | selector: row => row.carbs, 40 | sortable: true, 41 | right: true, 42 | }, 43 | { 44 | name: 'Protein (g)', 45 | selector: row => row.protein, 46 | sortable: true, 47 | right: true, 48 | }, 49 | { 50 | name: 'Sodium (mg)', 51 | selector: row => row.sodium, 52 | sortable: true, 53 | right: true, 54 | }, 55 | { 56 | name: 'Calcium (%)', 57 | selector: row => row.calcium, 58 | sortable: true, 59 | right: true, 60 | }, 61 | { 62 | name: 'Iron (%)', 63 | selector: row => row.iron, 64 | sortable: true, 65 | right: true, 66 | }, 67 | ]); 68 | 69 | class ClassicComponentStory extends PureComponent { 70 | state = { 71 | selectedRows: [], 72 | }; 73 | 74 | handleButtonClick = () => { 75 | console.log('clicked'); 76 | }; 77 | 78 | handleChange = state => { 79 | console.log('state', state.selectedRows); 80 | this.setState({ selectedRows: state.selectedRows }); 81 | }; 82 | 83 | render() { 84 | return ( 85 | 92 | ); 93 | } 94 | } 95 | 96 | const Template = args => ; 97 | 98 | export const ClassicComponent = Template.bind({}); 99 | 100 | ClassicComponent.args = {}; 101 | 102 | export default { 103 | title: 'Performance/Examples/Classic Component', 104 | component: ClassicComponent, 105 | }; 106 | -------------------------------------------------------------------------------- /stories/DataTable/OptimizedHook.stories.js: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useState, useCallback, useEffect } from 'react'; 2 | import tableDataItems from '../constants/sampleDesserts'; 3 | import DataTable from '../../src/index'; 4 | 5 | export const HookComponent = () => { 6 | const [selectedRows, setSelectedRows] = useState([]); 7 | 8 | useEffect(() => { 9 | // eslint-disable-next-line no-console 10 | console.log('state', selectedRows); 11 | }, [selectedRows]); 12 | 13 | const handleButtonClick = () => { 14 | // eslint-disable-next-line no-console 15 | console.log('clicked'); 16 | }; 17 | 18 | const handleChange = useCallback(state => { 19 | setSelectedRows(state.selectedRows); 20 | }, []); 21 | 22 | const columns = useMemo( 23 | () => [ 24 | { 25 | // eslint-disable-next-line react/button-has-type 26 | cell: () => , 27 | ignoreRowClick: true, 28 | allowOverflow: true, 29 | button: true, 30 | }, 31 | { 32 | name: 'Name', 33 | selector: row => row.name, 34 | sortable: true, 35 | grow: 2, 36 | }, 37 | { 38 | name: 'Type', 39 | selector: row => row.type, 40 | sortable: true, 41 | }, 42 | { 43 | name: 'Calories (g)', 44 | selector: row => row.calories, 45 | sortable: true, 46 | right: true, 47 | }, 48 | { 49 | name: 'Fat (g)', 50 | selector: row => row.fat, 51 | sortable: true, 52 | right: true, 53 | }, 54 | { 55 | name: 'Carbs (g)', 56 | selector: row => row.carbs, 57 | sortable: true, 58 | right: true, 59 | }, 60 | { 61 | name: 'Protein (g)', 62 | selector: row => row.protein, 63 | sortable: true, 64 | right: true, 65 | }, 66 | { 67 | name: 'Sodium (mg)', 68 | selector: row => row.sodium, 69 | sortable: true, 70 | right: true, 71 | }, 72 | { 73 | name: 'Calcium (%)', 74 | selector: row => row.calcium, 75 | sortable: true, 76 | right: true, 77 | }, 78 | { 79 | name: 'Iron (%)', 80 | selector: row => row.iron, 81 | sortable: true, 82 | right: true, 83 | }, 84 | ], 85 | [], 86 | ); 87 | 88 | return ( 89 | 96 | ); 97 | }; 98 | 99 | export default { 100 | title: 'Performance/Examples/Hook Component', 101 | component: HookComponent, 102 | }; 103 | -------------------------------------------------------------------------------- /stories/DataTable/Progess.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import data from '../constants/sampleMovieData'; 3 | import DataTable from '../../src/index'; 4 | 5 | const columns = [ 6 | { 7 | name: 'Title', 8 | selector: row => row.title, 9 | sortable: true, 10 | }, 11 | { 12 | name: 'Director', 13 | selector: row => row.director, 14 | sortable: true, 15 | }, 16 | { 17 | name: 'Year', 18 | selector: row => row.year, 19 | sortable: true, 20 | }, 21 | ]; 22 | 23 | export const Basic = () => { 24 | const [pending, setPending] = React.useState(true); 25 | const [rows, setRows] = React.useState([]); 26 | React.useEffect(() => { 27 | const timeout = setTimeout(() => { 28 | setRows(data); 29 | setPending(false); 30 | }, 2000); 31 | return () => clearTimeout(timeout); 32 | }, []); 33 | 34 | return ; 35 | }; 36 | 37 | export default { 38 | title: 'Loading/Basic', 39 | component: Basic, 40 | }; 41 | -------------------------------------------------------------------------------- /stories/DataTable/ProgessCustom.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled, { keyframes } from 'styled-components'; 3 | import data from '../constants/sampleMovieData'; 4 | import DataTable from '../../src/index'; 5 | 6 | const rotate360 = keyframes` 7 | from { 8 | transform: rotate(0deg); 9 | } 10 | 11 | to { 12 | transform: rotate(360deg); 13 | } 14 | `; 15 | 16 | const Spinner = styled.div` 17 | margin: 16px; 18 | animation: ${rotate360} 1s linear infinite; 19 | transform: translateZ(0); 20 | border-top: 2px solid grey; 21 | border-right: 2px solid grey; 22 | border-bottom: 2px solid grey; 23 | border-left: 4px solid black; 24 | background: transparent; 25 | width: 80px; 26 | height: 80px; 27 | border-radius: 50%; 28 | `; 29 | 30 | const columns = [ 31 | { 32 | name: 'Title', 33 | selector: row => row.title, 34 | sortable: true, 35 | }, 36 | { 37 | name: 'Director', 38 | selector: row => row.director, 39 | sortable: true, 40 | }, 41 | { 42 | name: 'Year', 43 | selector: row => row.year, 44 | sortable: true, 45 | }, 46 | ]; 47 | 48 | const CustomLoader = () => ( 49 |
50 | 51 |
Fancy Loader...
52 |
53 | ); 54 | 55 | export const Custom = () => { 56 | const [pending, setPending] = React.useState(true); 57 | const [rows, setRows] = React.useState([]); 58 | 59 | React.useEffect(() => { 60 | const timeout = setTimeout(() => { 61 | setRows(data); 62 | setPending(false); 63 | }, 2000); 64 | return () => clearTimeout(timeout); 65 | }, []); 66 | 67 | return ( 68 | } 74 | pagination 75 | /> 76 | ); 77 | }; 78 | 79 | export default { 80 | title: 'Loading/Custom', 81 | component: Custom, 82 | }; 83 | -------------------------------------------------------------------------------- /stories/DataTable/ThemeCustom.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import data from '../constants/sampleMovieData'; 3 | import DataTable, { createTheme } from '../../src/index'; 4 | 5 | // createTheme creates a new theme named solarized that overrides the build in dark theme 6 | createTheme( 7 | 'solarized', 8 | { 9 | text: { 10 | primary: '#268bd2', 11 | secondary: '#2aa198', 12 | }, 13 | background: { 14 | default: '#002b36', 15 | }, 16 | context: { 17 | background: '#cb4b16', 18 | text: '#FFFFFF', 19 | }, 20 | divider: { 21 | default: '#073642', 22 | }, 23 | button: { 24 | default: '#2aa198', 25 | hover: 'rgba(0,0,0,.08)', 26 | focus: 'rgba(255,255,255,.12)', 27 | disabled: 'rgba(255, 255, 255, .34)', 28 | }, 29 | sortFocus: { 30 | default: '#2aa198', 31 | }, 32 | }, 33 | 'dark', 34 | ); 35 | 36 | const columns = [ 37 | { 38 | name: 'Title', 39 | selector: row => row.title, 40 | sortable: true, 41 | }, 42 | { 43 | name: 'Director', 44 | selector: row => row.director, 45 | sortable: true, 46 | }, 47 | { 48 | name: 'Year', 49 | selector: row => row.year, 50 | sortable: true, 51 | }, 52 | ]; 53 | 54 | export const Custom = () => ( 55 | 56 | ); 57 | 58 | export default { 59 | title: 'Theming/Custom ', 60 | component: Custom, 61 | }; 62 | -------------------------------------------------------------------------------- /stories/DataTable/ThemeDark.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import data from '../constants/sampleMovieData'; 3 | import DataTable from '../../src/index'; 4 | 5 | const columns = [ 6 | { 7 | name: 'Title', 8 | selector: row => row.title, 9 | sortable: true, 10 | }, 11 | { 12 | name: 'Director', 13 | selector: row => row.director, 14 | sortable: true, 15 | }, 16 | { 17 | name: 'Year', 18 | selector: row => row.year, 19 | sortable: true, 20 | }, 21 | ]; 22 | 23 | const BuiltinStory = ({ theme }) => ( 24 | 35 | ); 36 | 37 | const Template = args => ; 38 | 39 | export const Builtin = Template.bind({}); 40 | 41 | Builtin.args = { 42 | theme: 'default', 43 | }; 44 | 45 | export default { 46 | title: 'Theming/Builtin', 47 | component: Builtin, 48 | argTypes: { 49 | theme: { 50 | options: ['default', 'dark'], 51 | control: { type: 'radio' }, 52 | description: 'toggle between light and dark themes', 53 | table: { 54 | type: { summary: 'string' }, 55 | defaultValue: { summary: 'default' }, 56 | }, 57 | }, 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /stories/DataTable/conditional/rows.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import tableDataItems from '../../constants/sampleDesserts'; 3 | import DataTable from '../../../src/index'; 4 | 5 | const columns = [ 6 | { 7 | name: 'Name', 8 | selector: row => row.name, 9 | sortable: true, 10 | fixed: true, 11 | }, 12 | { 13 | name: 'Type', 14 | selector: row => row.type, 15 | sortable: true, 16 | fixed: true, 17 | }, 18 | { 19 | name: 'Calories (g)', 20 | selector: row => row.calories, 21 | sortable: true, 22 | right: true, 23 | }, 24 | { 25 | name: 'Fat (g)', 26 | selector: row => row.fat, 27 | sortable: true, 28 | right: true, 29 | }, 30 | { 31 | name: 'Carbs (g)', 32 | selector: row => row.carbs, 33 | sortable: true, 34 | right: true, 35 | }, 36 | { 37 | name: 'Protein (g)', 38 | selector: row => row.protein, 39 | sortable: true, 40 | right: true, 41 | }, 42 | { 43 | name: 'Sodium (mg)', 44 | selector: row => row.sodium, 45 | sortable: true, 46 | right: true, 47 | }, 48 | { 49 | name: 'Calcium (%)', 50 | selector: row => row.calcium, 51 | sortable: true, 52 | right: true, 53 | }, 54 | { 55 | name: 'Iron (%)', 56 | selector: row => row.iron, 57 | sortable: true, 58 | right: true, 59 | }, 60 | ]; 61 | 62 | const conditionalRowStyles = [ 63 | { 64 | when: row => row.calories < 300, 65 | style: { 66 | backgroundColor: 'rgba(63, 195, 128, 0.9)', 67 | color: 'white', 68 | '&:hover': { 69 | cursor: 'pointer', 70 | }, 71 | }, 72 | }, 73 | { 74 | when: row => row.calories >= 300 && row.calories < 400, 75 | style: { 76 | backgroundColor: 'rgba(248, 148, 6, 0.9)', 77 | color: 'white', 78 | '&:hover': { 79 | cursor: 'pointer', 80 | }, 81 | }, 82 | }, 83 | { 84 | when: row => row.calories >= 400, 85 | style: { 86 | backgroundColor: 'rgba(242, 38, 19, 0.9)', 87 | color: 'white', 88 | '&:hover': { 89 | cursor: 'not-allowed', 90 | }, 91 | }, 92 | }, 93 | ]; 94 | 95 | export const Rows = () => ( 96 | 103 | ); 104 | 105 | export default { 106 | title: 'Conditional Rows/Rows', 107 | component: Rows, 108 | }; 109 | -------------------------------------------------------------------------------- /stories/DataTable/expandable/basic.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Expandable Rows 4 | 5 | Adding Expandable Rows functionality is easy. Let's make our rows expandable: 6 | 7 | ```js 8 | function MyComponent() { 9 | return ; 10 | } 11 | ``` 12 | 13 | ## Custom Expander component 14 | 15 | Next, we'll need to create a custom component to display our data and pass it into `DataTable`: 16 | 17 | ```js 18 | // data provides access to your row data 19 | const ExpandedComponent = ({ data }) =>
{JSON.stringify(data, null, 2)}
; 20 | 21 | function MyComponent() { 22 | return ; 23 | } 24 | ``` 25 | 26 | You'll notice that `expandableRowsComponent` has a function signtaure of `({ data }) => ...`. React Data Table handles passing your row `data` into your custom expandable component so you can access that rows fields. 27 | 28 | 29 | 30 | 31 | 32 | ## TypeScript 33 | 34 | ### Basic Expandable component 35 | 36 | With TypeScript we'll need to use the `ExpanderComponentProps` type and pass it our type or interface. Here's a fully working example: 37 | 38 | ```ts 39 | import * as React from 'react'; 40 | import DataTable, { ExpanderComponentProps } from 'react-data-table-component'; 41 | 42 | type DataRow = { 43 | title: string; 44 | director: string; 45 | year: string; 46 | }; 47 | 48 | const columns: TableColumn[] = [ 49 | { 50 | name: 'Title', 51 | selector: row => row.title, 52 | }, 53 | { 54 | name: 'Director', 55 | selector: row => row.director, 56 | }, 57 | { 58 | name: 'Year', 59 | selector: row => row.year, 60 | }, 61 | ]; 62 | 63 | // data provides access to your row data 64 | const ExpandedComponent: React.FC> = ({ data }) => { 65 | return
{JSON.stringify(data, null, 2)}
; 66 | }; 67 | 68 | function MyComponent() { 69 | return ; 70 | } 71 | ``` 72 | 73 | ### expandableRowsComponentProps 74 | 75 | expandableRowsComponentProps allows you to pass additional props to your expander component and prevents TypeScript from complaining: 76 | 77 | ```ts 78 | import * as React from 'react'; 79 | import DataTable, { ExpanderComponentProps } from 'react-data-table-component'; 80 | 81 | interface Row { 82 | title: string; 83 | director: string; 84 | year: string; 85 | } 86 | 87 | interface Props extends ExpanderComponentProps { 88 | // currently, props that extend ExpanderComponentProps must be set to optional. 89 | someTitleProp?: string; 90 | } 91 | 92 | const ExpandableRowComponent: React.FC = ({ data, someTitleProp }) => { 93 | return ( 94 | <> 95 |

{someTitleProp}

96 |

{data.title}

97 |

{data.director}

98 |

{data.year}

99 | 100 | ); 101 | }; 102 | ``` 103 | 104 | ## Accessibility 105 | 106 | You can tab through expanders and use the space bar or enter keys to expand. 107 | -------------------------------------------------------------------------------- /stories/DataTable/expandable/basic.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import doc from './basic.mdx'; 3 | import tableDataItems from '../../constants/sampleMovieData'; 4 | import DataTable from '../../../src/index'; 5 | 6 | const ExpandedComponent = ({ data }) =>
{JSON.stringify(data, null, 2)}
; 7 | 8 | const columns = [ 9 | { 10 | name: 'Title', 11 | selector: row => row.title, 12 | sortable: true, 13 | }, 14 | { 15 | name: 'Director', 16 | selector: row => row.director, 17 | sortable: true, 18 | }, 19 | { 20 | name: 'Year', 21 | selector: row => row.year, 22 | sortable: true, 23 | }, 24 | ]; 25 | 26 | const BasicStory = ({ expandableRows, expandOnRowClicked, expandOnRowDoubleClicked, expandableRowsHideExpander }) => ( 27 | 38 | ); 39 | 40 | const Template = args => ; 41 | 42 | export const Basic = Template.bind({}); 43 | 44 | Basic.args = { 45 | expandableRows: true, 46 | expandOnRowClicked: false, 47 | expandOnRowDoubleClicked: false, 48 | expandableRowsHideExpander: false, 49 | }; 50 | 51 | export default { 52 | title: 'Expandable/Basic', 53 | component: Basic, 54 | parameters: { 55 | docs: { 56 | page: doc, 57 | }, 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /stories/DataTable/expandable/preDisabled.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Pre Disabled Rows 4 | 5 | To pre-disable rows based on your data: 6 | 7 | ```js 8 | ... 9 | const rowPreDisabled = row => row.disabled; 10 | 11 | function MyComponent() { 12 | return ( 13 | 19 | ); 20 | } 21 | ``` 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /stories/DataTable/expandable/preDisabled.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import doc from './preDisabled.mdx'; 3 | import tableDataItems from '../../constants/sampleMovieData'; 4 | import DataTable from '../../../src/index'; 5 | 6 | const ExpandedComponent = ({ data }) =>
{JSON.stringify(data, null, 2)}
; 7 | 8 | const columns = [ 9 | { 10 | name: 'Title', 11 | selector: row => row.title, 12 | sortable: true, 13 | }, 14 | { 15 | name: 'Director', 16 | selector: row => row.director, 17 | sortable: true, 18 | }, 19 | { 20 | name: 'Year', 21 | selector: row => row.year, 22 | sortable: true, 23 | }, 24 | ]; 25 | 26 | export const PreDisabled = () => { 27 | const data = tableDataItems.map(item => { 28 | let disabled = false; 29 | if (Number(item.year) < 2000) { 30 | disabled = true; 31 | } 32 | return { ...item, disabled }; 33 | }); 34 | return ( 35 | row.disabled} 41 | expandableRowsComponent={ExpandedComponent} 42 | pagination 43 | /> 44 | ); 45 | }; 46 | 47 | export default { 48 | title: 'Expandable/Pre Disabled ', 49 | component: PreDisabled, 50 | parameters: { 51 | docs: { 52 | page: doc, 53 | }, 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /stories/DataTable/expandable/preExpanded.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Pre Selected Rows 4 | 5 | To pre-select rows based on your data: 6 | 7 | ```js 8 | ... 9 | const rowPreExpanded = row => row.defaultExpanded 10 | 11 | function MyComponent() { 12 | return ( 13 | 19 | ); 20 | } 21 | ``` 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /stories/DataTable/expandable/preExpanded.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import doc from './preExpanded.mdx'; 3 | import tableDataItems from '../../constants/sampleMovieData'; 4 | import DataTable from '../../../src/index'; 5 | 6 | const ExpandedComponent = ({ data }) =>
{JSON.stringify(data, null, 2)}
; 7 | 8 | const columns = [ 9 | { 10 | name: 'Title', 11 | selector: row => row.title, 12 | sortable: true, 13 | }, 14 | { 15 | name: 'Director', 16 | selector: row => row.director, 17 | sortable: true, 18 | }, 19 | { 20 | name: 'Year', 21 | selector: row => row.year, 22 | sortable: true, 23 | }, 24 | ]; 25 | 26 | export const PreExpanded = () => { 27 | const data = tableDataItems; 28 | data[0].defaultExpanded = true; 29 | 30 | return ( 31 | row.defaultExpanded} 37 | expandableRowsComponent={ExpandedComponent} 38 | pagination 39 | /> 40 | ); 41 | }; 42 | 43 | export default { 44 | title: 'Expandable/Pre Expanded ', 45 | component: PreExpanded, 46 | parameters: { 47 | docs: { 48 | page: doc, 49 | }, 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /stories/DataTable/material-ui/linearProgressBar.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Material Progress 4 | 5 | Adding a custom progress bar is easy, in this case let's add Material UI's Linear Progress bar by passing in a `progressComponent`: 6 | 7 | ```js 8 | import React from 'react'; 9 | import doc from './linearProgressBar.mdx'; 10 | import { makeStyles } from '@material-ui/core/styles'; 11 | import LinearProgress from '@material-ui/core/LinearProgress'; 12 | import data from '../../constants/sampleMovieData'; 13 | import DataTable from '../../../src/index'; 14 | 15 | const useStyles = makeStyles(theme => ({ 16 | root: { 17 | width: '100%', 18 | '& > * + *': { 19 | marginTop: theme.spacing(2), 20 | }, 21 | }, 22 | })); 23 | 24 | // The Material UI way... 25 | const LinearIndeterminate = () => { 26 | const classes = useStyles(); 27 | 28 | return ( 29 |
30 | 31 |
32 | ); 33 | }; 34 | 35 | function MyComponent() { 36 | return ( 37 | } 42 | /> 43 | ); 44 | }; 45 | ``` 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /stories/DataTable/material-ui/linearProgressBar.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import doc from './linearProgressBar.mdx'; 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import LinearProgress from '@material-ui/core/LinearProgress'; 5 | import data from '../../constants/sampleMovieData'; 6 | import DataTable from '../../../src/index'; 7 | 8 | const useStyles = makeStyles(theme => ({ 9 | root: { 10 | width: '100%', 11 | '& > * + *': { 12 | marginTop: theme.spacing(2), 13 | }, 14 | }, 15 | })); 16 | 17 | const LinearIndeterminate = () => { 18 | const classes = useStyles(); 19 | 20 | return ( 21 |
22 | 23 |
24 | ); 25 | }; 26 | 27 | const columns = [ 28 | { 29 | name: 'Title', 30 | selector: 'title', 31 | sortable: true, 32 | }, 33 | { 34 | name: 'Director', 35 | selector: 'director', 36 | sortable: true, 37 | }, 38 | { 39 | name: 'Year', 40 | selector: 'year', 41 | sortable: true, 42 | }, 43 | ]; 44 | 45 | const ProgressStory = ({ progressPending, persistTableHead }) => { 46 | return ( 47 | } 53 | persistTableHead={persistTableHead} 54 | pagination 55 | /> 56 | ); 57 | }; 58 | 59 | const Template = args => ; 60 | 61 | export const Progress = Template.bind({}); 62 | 63 | Progress.args = { 64 | persistTableHead: false, 65 | progressPending: true, 66 | }; 67 | 68 | export default { 69 | title: 'UI Library/Material UI/Progress', 70 | component: Progress, 71 | parameters: { 72 | docs: { 73 | page: doc, 74 | }, 75 | }, 76 | }; 77 | -------------------------------------------------------------------------------- /stories/DataTable/material-ui/pagination.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Material Pagination Component 4 | 5 | React Data Table already has a "Material" looking Pagination, but if you want to go a step further you can integration Material UI's Pagination component. 6 | 7 | Using the `paginationComponent` property is you can access React Data Tables internal pagination properties. 8 | 9 | ```js 10 | ({ rowsPerPage, rowCount, onChangePage, onChangeRowsPerPage, currentPage }) => ... 11 | ``` 12 | 13 | ## Import and Create our Material UI Pagination Component 14 | 15 | ```js 16 | import React from 'react'; 17 | import TablePagination from '@material-ui/core/TablePagination'; 18 | import IconButton from '@material-ui/core/IconButton'; 19 | import FirstPageIcon from '@material-ui/icons/FirstPage'; 20 | import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft'; 21 | import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight'; 22 | import LastPageIcon from '@material-ui/icons/LastPage'; 23 | 24 | function TablePaginationActions({ count, page, rowsPerPage, onChangePage }) { 25 | const handleFirstPageButtonClick = () => { 26 | onChangePage(1); 27 | }; 28 | 29 | // RDT uses page index starting at 1, MUI starts at 0 30 | // i.e. page prop will be off by one here 31 | const handleBackButtonClick = () => { 32 | onChangePage(page); 33 | }; 34 | 35 | const handleNextButtonClick = () => { 36 | onChangePage(page + 2); 37 | }; 38 | 39 | const handleLastPageButtonClick = () => { 40 | onChangePage(getNumberOfPages(count, rowsPerPage)); 41 | }; 42 | 43 | return ( 44 | <> 45 | 46 | 47 | 48 | 49 | 50 | 51 | = getNumberOfPages(count, rowsPerPage) - 1} 54 | aria-label="next page" 55 | > 56 | 57 | 58 | = getNumberOfPages(count, rowsPerPage) - 1} 61 | aria-label="last page" 62 | > 63 | 64 | 65 | 66 | ); 67 | } 68 | 69 | const CustomMaterialPagination = ({ rowsPerPage, rowCount, onChangePage, onChangeRowsPerPage, currentPage }) => ( 70 | onChangeRowsPerPage(Number(target.value))} 77 | ActionsComponent={TablePaginationActions} 78 | /> 79 | ); 80 | 81 | export default CustomMaterialPagination' 82 | ``` 83 | 84 | You can now pass `CustomMaterialPagination` to `paginationComponent` 85 | 86 | ```js 87 | ... 88 | 94 | ... 95 | ``` 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /stories/DataTable/material-ui/pagination.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import doc from './pagination.mdx'; 3 | import TablePagination from '@material-ui/core/TablePagination'; 4 | import IconButton from '@material-ui/core/IconButton'; 5 | import FirstPageIcon from '@material-ui/icons/FirstPage'; 6 | import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft'; 7 | import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight'; 8 | import LastPageIcon from '@material-ui/icons/LastPage'; 9 | import { getNumberOfPages } from '../../../src/DataTable/util'; 10 | import DataTable from '../../../src/index'; 11 | import data from '../../constants/sampleMovieData'; 12 | 13 | const columns = [ 14 | { 15 | name: 'Title', 16 | selector: row => row.title, 17 | sortable: true, 18 | }, 19 | { 20 | name: 'Director', 21 | selector: row => row.director, 22 | sortable: true, 23 | }, 24 | { 25 | name: 'Year', 26 | selector: row => row.year, 27 | sortable: true, 28 | }, 29 | ]; 30 | 31 | function TablePaginationActions({ count, page, rowsPerPage, onChangePage }) { 32 | const handleFirstPageButtonClick = () => { 33 | onChangePage(1); 34 | }; 35 | 36 | // RDT uses page index starting at 1, MUI starts at 0 37 | // i.e. page prop will be off by one here 38 | const handleBackButtonClick = () => { 39 | onChangePage(page); 40 | }; 41 | 42 | const handleNextButtonClick = () => { 43 | onChangePage(page + 2); 44 | }; 45 | 46 | const handleLastPageButtonClick = () => { 47 | onChangePage(getNumberOfPages(count, rowsPerPage)); 48 | }; 49 | 50 | return ( 51 | <> 52 | 53 | 54 | 55 | 56 | 57 | 58 | = getNumberOfPages(count, rowsPerPage) - 1} 61 | aria-label="next page" 62 | > 63 | 64 | 65 | = getNumberOfPages(count, rowsPerPage) - 1} 68 | aria-label="last page" 69 | > 70 | 71 | 72 | 73 | ); 74 | } 75 | 76 | export const CustomMaterialPagination = ({ rowsPerPage, rowCount, onChangePage, onChangeRowsPerPage, currentPage }) => ( 77 | onChangeRowsPerPage(Number(target.value))} 84 | ActionsComponent={TablePaginationActions} 85 | /> 86 | ); 87 | 88 | export function Pagination() { 89 | return ( 90 | 97 | ); 98 | } 99 | 100 | export default { 101 | title: 'UI Library/Material UI/Pagination', 102 | component: Pagination, 103 | parameters: { 104 | docs: { 105 | page: doc, 106 | }, 107 | }, 108 | }; 109 | -------------------------------------------------------------------------------- /stories/DataTable/material-ui/table.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Make Our Table Material Design 4 | 5 | Let's override the built-in checkboxes and sort icon with some [Material Ui](https://material-ui.com) sexiness: 6 | 7 | ```js 8 | ... 9 | import Checkbox from '@mataerial-ui/core/Checkbox'; 10 | import ArrowDownward from '@material-ui/icons/ArrowDownward'; 11 | 12 | ... 13 | 14 | const MyComponent = () => ( 15 | } // use a material icon for our sort icon. rdt will rotate the icon 180 degrees for you 23 | /> 24 | ); 25 | ``` 26 | 27 | ## Using Custom Checkboxes and Indeterminate State 28 | 29 | Sometimes UI Library checkbox components have their own way of handling indeterminate state. We don't want React Data Table hard coded to a specific ui lib or custom component, so instead a "hook" is provided to allow you to pass a function that will be resolved by React Data Table's internal `Checkbox` for use with `indeterminate` functionality. 30 | 31 | Example Usage: 32 | 33 | ```js 34 | 35 | import Checkbox from '@mataerial-ui/core/Checkbox'; 36 | 37 | ... 38 | 39 | /* 40 | In this example, the Material Ui ui lib determines its own indeterminate state via the `indeterminate` property. 41 | Let's override it using selectableRowsComponentProps` 42 | */ 43 | const selectProps = { indeterminate: isIndeterminate => isIndeterminate }; 44 | 45 | const MyComponent = () => ( 46 | 54 | ); 55 | ``` 56 | 57 | **Note** This is currently only supported for indeterminate state, but may be expanded in the future if there is a demand. 58 | 59 | # Material Compliant Table! 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /stories/DataTable/material-ui/table.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import doc from './table.mdx'; 3 | import differenceBy from 'lodash/differenceBy'; 4 | import Card from '@material-ui/core/Card'; 5 | import IconButton from '@material-ui/core/IconButton'; 6 | import Checkbox from '@material-ui/core/Checkbox'; 7 | import ArrowDownward from '@material-ui/icons/ArrowDownward'; 8 | import Delete from '@material-ui/icons/Delete'; 9 | import Add from '@material-ui/icons/Add'; 10 | import tableDataItems from '../../constants/sampleDesserts'; 11 | import DataTable from '../../../src/index'; 12 | 13 | const sortIcon = ; 14 | const selectProps = { indeterminate: isIndeterminate => isIndeterminate }; 15 | const actions = ( 16 | 17 | 18 | 19 | ); 20 | const contextActions = deleteHandler => ( 21 | 22 | 23 | 24 | ); 25 | 26 | const columns = [ 27 | { 28 | name: 'Name', 29 | selector: row => row.name, 30 | sortable: true, 31 | grow: 2, 32 | reorder: true, 33 | }, 34 | { 35 | name: 'Type', 36 | selector: row => row.type, 37 | sortable: true, 38 | reorder: true, 39 | }, 40 | { 41 | name: 'Calories (g)', 42 | selector: row => row.calories, 43 | sortable: true, 44 | right: true, 45 | reorder: true, 46 | }, 47 | { 48 | name: 'Fat (g)', 49 | selector: row => row.fat, 50 | sortable: true, 51 | right: true, 52 | reorder: true, 53 | }, 54 | { 55 | name: 'Carbs (g)', 56 | selector: row => row.carbs, 57 | sortable: true, 58 | right: true, 59 | reorder: true, 60 | }, 61 | { 62 | name: 'Protein (g)', 63 | selector: row => row.protein, 64 | sortable: true, 65 | right: true, 66 | reorder: true, 67 | }, 68 | { 69 | name: 'Sodium (mg)', 70 | selector: row => row.sodium, 71 | sortable: true, 72 | right: true, 73 | reorder: true, 74 | }, 75 | { 76 | name: 'Calcium (%)', 77 | selector: row => row.calcium, 78 | sortable: true, 79 | right: true, 80 | reorder: true, 81 | }, 82 | { 83 | name: 'Iron (%)', 84 | selector: row => row.iron, 85 | sortable: true, 86 | right: true, 87 | reorder: true, 88 | }, 89 | ]; 90 | 91 | function MaterialStory({ selectableRows, expandableRows }) { 92 | const [selectedRows, setSelectedRows] = React.useState([]); 93 | const [toggleCleared, setToggleCleared] = React.useState(false); 94 | const [data, setData] = React.useState(tableDataItems); 95 | 96 | const handleChange = () => { 97 | setSelectedRows(selectedRows); 98 | }; 99 | 100 | const handleRowClicked = row => { 101 | // eslint-disable-next-line no-console 102 | console.log(`${row.name} was clicked!`); 103 | }; 104 | 105 | const deleteAll = () => { 106 | const rows = selectedRows.map(r => r.name); 107 | // eslint-disable-next-line no-alert 108 | if (window.confirm(`Are you sure you want to delete:\r ${rows}?`)) { 109 | setToggleCleared(!toggleCleared); 110 | setData(differenceBy(data, selectedRows, 'name')); 111 | } 112 | }; 113 | 114 | return ( 115 | 116 | 135 | 136 | ); 137 | } 138 | 139 | const Template = args => ; 140 | 141 | export const Table = Template.bind({}); 142 | 143 | Table.args = { 144 | selectableRows: true, 145 | expandableRows: true, 146 | }; 147 | 148 | export default { 149 | title: 'UI Library/Material UI/Table', 150 | component: Table, 151 | parameters: { 152 | docs: { 153 | page: doc, 154 | }, 155 | }, 156 | }; 157 | -------------------------------------------------------------------------------- /stories/DataTable/pagination/basic.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Pagination 4 | 5 | Adding client side pagination functionality is easy: 6 | 7 | ```js 8 | function MyComponent() { 9 | return ( 10 | 15 | ); 16 | }; 17 | ``` 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /stories/DataTable/pagination/basic.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import doc from './basic.mdx'; 3 | import data from '../../constants/sampleMovieData'; 4 | import DataTable from '../../../src/index'; 5 | 6 | const columns = [ 7 | { 8 | name: 'Title', 9 | selector: row => row.title, 10 | sortable: true, 11 | }, 12 | { 13 | name: 'Director', 14 | selector: row => row.director, 15 | sortable: true, 16 | }, 17 | { 18 | name: 'Year', 19 | selector: row => row.year, 20 | sortable: true, 21 | }, 22 | ]; 23 | 24 | export const Basic = () => ; 25 | 26 | export default { 27 | title: 'Pagination/Basic', 28 | component: Basic, 29 | parameters: { 30 | docs: { 31 | page: doc, 32 | }, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /stories/DataTable/pagination/options.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Pagination Component Options 4 | 5 | `paginationComponentOptions`allow you to provide options to the built-in in Pagination component. 6 | 7 | | Property | Type | Description | 8 | | --------------------- | ------- | ----------------------------------------------------- | 9 | | noRowsPerPage | boolean | hide the rows per page dropdown | 10 | | rowsPerPageText | string | change the rows per page text | 11 | | rangeSeparatorText | string | change the seperator text e.g. 1 - 10 'of' 100 rows | 12 | | selectAllRowsItem | boolean | show 'All' as an option in the rows per page dropdown | 13 | | selectAllRowsItemText | string | change the rows per page text | 14 | 15 | ```js 16 | const paginationComponentOptions = { 17 | rowsPerPageText: 'Filas por página', 18 | rangeSeparatorText: 'de', 19 | selectAllRowsItem: true, 20 | selectAllRowsItemText: 'Todos', 21 | }; 22 | 23 | function MyComponent() { 24 | return ; 25 | } 26 | ``` 27 | 28 | ## Custom Pagination Component `paginationComponentOptions` 29 | 30 | When using `paginationComponent` the `paginationComponentOptions` above become irrelevent. Instead, `paginationComponentOptions` can be used to pass whatever options you need to your custom Pagination component 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /stories/DataTable/pagination/options.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import doc from './options.mdx'; 3 | import data from '../../constants/sampleMovieData'; 4 | import DataTable from '../../../src/index'; 5 | 6 | const columns = [ 7 | { 8 | name: 'Título', 9 | selector: row => row.title, 10 | sortable: true, 11 | }, 12 | { 13 | name: 'Director', 14 | selector: row => row.director, 15 | sortable: true, 16 | }, 17 | { 18 | name: 'Año', 19 | selector: row => row.year, 20 | sortable: true, 21 | }, 22 | ]; 23 | 24 | const paginationComponentOptions = { 25 | rowsPerPageText: 'Filas por página', 26 | rangeSeparatorText: 'de', 27 | selectAllRowsItem: true, 28 | selectAllRowsItemText: 'Todos', 29 | }; 30 | 31 | export const Options = () => ( 32 | 39 | ); 40 | 41 | export default { 42 | title: 'Pagination/Options', 43 | component: Options, 44 | parameters: { 45 | docs: { 46 | page: doc, 47 | }, 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /stories/DataTable/pagination/remote.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Remote Pagination 4 | 5 | To enable remote/manual pagination you'll need to add the `paginationServer` property and ensure the remote API you are using supports pagination metadata. 6 | 7 | You'll also need to implement the `onChangeRowsPerPage`, and `onChangePage` callbacks. 8 | 9 | Finally, you'll need to keep track of your total rows using `paginationTotalRows`. This should be obtained from the remote API you are calling from to get the pagnination records. 10 | 11 | The following covers a working example of remote pagniation: 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /stories/DataTable/pagination/remote.stories.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import doc from './remote.mdx'; 3 | import axios from 'axios'; 4 | import DataTable from '../../../src/index'; 5 | 6 | const columns = [ 7 | { 8 | name: 'First Name', 9 | selector: row => row.first_name, 10 | sortable: true, 11 | }, 12 | { 13 | name: 'Last Name', 14 | selector: row => row.last_name, 15 | sortable: true, 16 | }, 17 | { 18 | name: 'Email', 19 | selector: row => row.email, 20 | sortable: true, 21 | }, 22 | ]; 23 | 24 | export const Remote = () => { 25 | const [data, setData] = useState([]); 26 | const [loading, setLoading] = useState(false); 27 | const [totalRows, setTotalRows] = useState(0); 28 | const [perPage, setPerPage] = useState(10); 29 | 30 | const fetchUsers = async page => { 31 | setLoading(true); 32 | 33 | const response = await axios.get(`https://reqres.in/api/users?page=${page}&per_page=${perPage}&delay=1`); 34 | 35 | setData(response.data.data); 36 | setTotalRows(response.data.total); 37 | setLoading(false); 38 | }; 39 | 40 | const handlePageChange = page => { 41 | fetchUsers(page); 42 | }; 43 | 44 | const handlePerRowsChange = async (newPerPage, page) => { 45 | setLoading(true); 46 | 47 | const response = await axios.get(`https://reqres.in/api/users?page=${page}&per_page=${newPerPage}&delay=1`); 48 | 49 | setData(response.data.data); 50 | setPerPage(newPerPage); 51 | setLoading(false); 52 | }; 53 | 54 | useEffect(() => { 55 | fetchUsers(1); // fetch page 1 of users 56 | // eslint-disable-next-line react-hooks/exhaustive-deps 57 | }, []); 58 | 59 | return ( 60 | 71 | ); 72 | }; 73 | 74 | export default { 75 | title: 'Pagination/Remote', 76 | component: Remote, 77 | parameters: { 78 | docs: { 79 | page: doc, 80 | }, 81 | }, 82 | }; 83 | -------------------------------------------------------------------------------- /stories/DataTable/selectableRows/basic.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Selectable Rows 4 | 5 | Adding Selectable Rows functionality is easy. Let's make our rows selectable and also access selected results `onSelectedRowsChange`: 6 | 7 | ```js 8 | function MyComponent() { 9 | const handleChange = ({ selectedRows }) => { 10 | // You can set state or dispatch with something like Redux so we can use the retrieved data 11 | console.log('Selected Rows: ', selectedRows); 12 | }; 13 | 14 | return ( 15 | 21 | ); 22 | }; 23 | ``` 24 | 25 | 26 | 27 | 28 | 29 | ## Accessibility 30 | 31 | You can tab through checkboxes and use the space bar to expand. 32 | -------------------------------------------------------------------------------- /stories/DataTable/selectableRows/basic.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import doc from './basic.mdx'; 3 | import data from '../../constants/sampleMovieData'; 4 | import DataTable from '../../../src/index'; 5 | 6 | const columns = [ 7 | { 8 | name: 'Title', 9 | selector: row => row.title, 10 | sortable: true, 11 | }, 12 | { 13 | name: 'Director', 14 | selector: row => row.director, 15 | sortable: true, 16 | }, 17 | { 18 | name: 'Year', 19 | selector: row => row.year, 20 | sortable: true, 21 | }, 22 | ]; 23 | 24 | export const Basic = () => ; 25 | 26 | export default { 27 | title: 'Selectable/Basic', 28 | component: Basic, 29 | parameters: { 30 | docs: { 31 | page: doc, 32 | }, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /stories/DataTable/selectableRows/preDisabled.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Pre Disabled Rows 4 | 5 | To pre-disable rows based on your data: 6 | 7 | ```js 8 | ... 9 | const rowDisabledCriteria = row => row.isOutOfStock; 10 | 11 | function MyComponent() { 12 | return ( 13 | 19 | ); 20 | } 21 | ``` 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /stories/DataTable/selectableRows/preDisabled.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import doc from './preDisabled.mdx'; 3 | import DataTable from '../../../src/index'; 4 | import data from '../../constants/sampleDesserts'; 5 | 6 | const columns = [ 7 | { 8 | name: 'Name', 9 | selector: row => row.name, 10 | sortable: true, 11 | }, 12 | { 13 | name: 'Type', 14 | selector: row => row.type, 15 | sortable: true, 16 | }, 17 | { 18 | name: 'Out of Stock', 19 | selector: row => row.isOutOfStock, 20 | sortable: true, 21 | cell: row =>
{row.isOutOfStock ? 'Yes' : 'No'}
, 22 | }, 23 | { 24 | name: 'Calories (g)', 25 | selector: row => row.calories, 26 | sortable: true, 27 | right: true, 28 | }, 29 | { 30 | name: 'Fat (g)', 31 | selector: row => row.fat, 32 | sortable: true, 33 | right: true, 34 | }, 35 | { 36 | name: 'Carbs (g)', 37 | selector: row => row.carbs, 38 | sortable: true, 39 | }, 40 | ]; 41 | 42 | const customData = data.map(datum => ({ ...datum, isOutOfStock: false })); 43 | 44 | customData[1].isOutOfStock = true; 45 | customData[3].isOutOfStock = true; 46 | 47 | const rowDisabledCriteria = row => row.isOutOfStock; 48 | 49 | export const PreDisabled = () => ( 50 | 58 | ); 59 | 60 | export default { 61 | title: 'Selectable/Pre Disabled', 62 | component: PreDisabled, 63 | parameters: { 64 | docs: { 65 | page: doc, 66 | }, 67 | }, 68 | }; 69 | -------------------------------------------------------------------------------- /stories/DataTable/selectableRows/preSelected.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Pre Selected Rows 4 | 5 | To pre-select rows based on your data: 6 | 7 | ```js 8 | ... 9 | const rowSelectCritera = row => row.fat > 6; 10 | 11 | function MyComponent() { 12 | return ( 13 | 19 | ); 20 | } 21 | ``` 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /stories/DataTable/selectableRows/preSelected.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import doc from './preSelected.mdx'; 3 | import DataTable from '../../../src/index'; 4 | import data from '../../constants/sampleDesserts'; 5 | 6 | const columns = [ 7 | { 8 | name: 'Name', 9 | selector: row => row.name, 10 | sortable: true, 11 | }, 12 | { 13 | name: 'Type', 14 | selector: row => row.type, 15 | sortable: true, 16 | }, 17 | { 18 | name: 'Calories (g)', 19 | selector: row => row.calories, 20 | sortable: true, 21 | right: true, 22 | }, 23 | { 24 | name: 'Fat (g)', 25 | selector: row => row.fat, 26 | sortable: true, 27 | right: true, 28 | }, 29 | { 30 | name: 'Carbs (g)', 31 | selector: row => row.carbs, 32 | sortable: true, 33 | right: true, 34 | }, 35 | ]; 36 | 37 | const rowSelectCritera = row => row.fat > 6; 38 | 39 | export const PreSelected = () => ( 40 | 48 | ); 49 | 50 | export default { 51 | title: 'Selectable/Pre Selected', 52 | component: PreSelected, 53 | parameters: { 54 | docs: { 55 | page: doc, 56 | }, 57 | }, 58 | }; 59 | -------------------------------------------------------------------------------- /stories/DataTable/selectableRows/rowMGMT.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Managing Row Selections 4 | 5 | Managing row selection with RDT is easy! We'll need to to a few things: 6 | 7 | 1. Manage our selected rows state 8 | 2. Toggle all row selection when an action is completed 9 | 10 | 11 | # Clearing Row Selections 12 | 13 | We need some hook to trigger all the selected rows to clear. If you were building your own table component, you would manage the selected rows state in some parent component, however, in our case since we to keep row management within React Data Table `clearSelectedRows` prop is provided so you can pass a toggled state. 14 | 15 | It will be up to you to make sure you do not pass the same state twice. For example, if you set `clearSelectedRows={true}` twice, on the second update/trigger, none the rows will not be cleared, so you will need to toggle the state. 16 | 17 | ```js 18 | function MyComponent() { 19 | const [selectedRows, setSelectedRows] = React.useState(false); 20 | const [toggledClearRows, setToggleClearRows] = React.useState(false); 21 | 22 | const handleChange = ({ selectedRows }) => { 23 | setSelectedRows(selectedRows); 24 | }; 25 | 26 | // Toggle the state so React Data Table changes to clearSelectedRows are triggered 27 | const handleClearRows = () => { 28 | setToggleClearRows(!toggledClearRows); 29 | } 30 | 31 | return ( 32 | <> 33 | 36 | 44 | 45 | ); 46 | }; 47 | ``` 48 | 49 | # Row Selections Delete Use Case 50 | 51 | Let's put these concepts together into a real world example. The following example allows you to manage deleting rows using `selectableRows`, `onSelectedRowsChange` and `clearSelectedRows`: 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /stories/DataTable/selectableRows/rowMGMT.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import doc from './rowMGMT.mdx'; 3 | import differenceBy from 'lodash/differenceBy'; 4 | import Button from '../../shared/Button'; 5 | import tableDataItems from '../../constants/sampleMovieData'; 6 | import DataTable from '../../../src/index'; 7 | 8 | const columns = [ 9 | { 10 | name: 'Title', 11 | selector: row => row.title, 12 | sortable: true, 13 | }, 14 | { 15 | name: 'Director', 16 | selector: row => row.director, 17 | sortable: true, 18 | }, 19 | { 20 | name: 'Year', 21 | selector: row => row.year, 22 | sortable: true, 23 | }, 24 | ]; 25 | 26 | export const ManageSelections = () => { 27 | const [selectedRows, setSelectedRows] = React.useState([]); 28 | const [toggleCleared, setToggleCleared] = React.useState(false); 29 | const [data, setData] = React.useState(tableDataItems); 30 | 31 | const handleRowSelected = React.useCallback(state => { 32 | setSelectedRows(state.selectedRows); 33 | }, []); 34 | 35 | const contextActions = React.useMemo(() => { 36 | const handleDelete = () => { 37 | // eslint-disable-next-line no-alert 38 | if (window.confirm(`Are you sure you want to delete:\r ${selectedRows.map(r => r.title)}?`)) { 39 | setToggleCleared(!toggleCleared); 40 | setData(differenceBy(data, selectedRows, 'title')); 41 | } 42 | }; 43 | 44 | return ( 45 | 48 | ); 49 | }, [data, selectedRows, toggleCleared]); 50 | 51 | return ( 52 | 62 | ); 63 | }; 64 | 65 | export default { 66 | title: 'Selectable/Manage Selections', 67 | component: ManageSelections, 68 | parameters: { 69 | docs: { 70 | page: doc, 71 | }, 72 | }, 73 | }; 74 | -------------------------------------------------------------------------------- /stories/DataTable/sorting/basic.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Sorting 4 | 5 | Enabling Sorting in React Data Table is easy. Simply add the `sortable` prop to any column definition you want to be sortable. Also make sure a `selector` has been defined. 6 | 7 | ```js 8 | const columns = [ 9 | { 10 | name: 'Title', 11 | selector: row => row.title, 12 | sortable: true, 13 | }, 14 | { 15 | name: 'Director', 16 | selector: row => row.director, 17 | sortable: true, 18 | }, 19 | { 20 | name: 'Year', 21 | selector: row => row.year, 22 | sortable: true, 23 | }, 24 | ]; 25 | ``` 26 | 27 | 28 | 29 | 30 | 31 | # Default Sort Field 32 | 33 | To load your table with a pre sorted column specify the `defaultSortFieldId`. The `defaultSortFieldId` will correspond to the order of your columns starting at the number 1: 34 | 35 | ```js 36 | const columns = [ 37 | 38 | ``` 39 | 40 | You can also specify your own column `id` as long as each column definition `id` is unique. 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /stories/DataTable/sorting/basic.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import doc from './basic.mdx'; 3 | import data from '../../constants/sampleMovieData'; 4 | import DataTable from '../../../src/index'; 5 | 6 | const columns = [ 7 | { 8 | name: 'Title', 9 | selector: row => row.title, 10 | sortable: true, 11 | }, 12 | { 13 | name: 'Director', 14 | selector: row => row.director, 15 | sortable: true, 16 | }, 17 | { 18 | name: 'Year', 19 | selector: row => row.year, 20 | sortable: true, 21 | }, 22 | ]; 23 | 24 | export const Basic = () => { 25 | return ; 26 | }; 27 | 28 | export default { 29 | title: 'Sorting/Basic', 30 | component: Basic, 31 | parameters: { 32 | docs: { 33 | page: doc, 34 | }, 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /stories/DataTable/sorting/remote.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Remote Sorting 4 | 5 | Remote/Server-side sorting via `sortServer` disables the internal sorting. It will be up to you to implement your own sort logic. A common use case is if you want to use remote sorting via an external API. 6 | 7 | If you just want to replace the internal sort logic you should use [sortFunction](/docs/sorting-custom-sort--custom-sort). 8 | 9 | For remote sorting you'll also want to set the `column` `sortField` to a value required by your API for that field. 10 | 11 | ```js 12 | const columns = [ 13 | { 14 | name: 'Title', 15 | selector: row => row.title, 16 | sortable: true, 17 | sortField: 'title', 18 | }, 19 | { 20 | name: 'Director', 21 | selector: row => row.director, 22 | sortable: true, 23 | sortField: 'director', 24 | }, 25 | { 26 | name: 'Year', 27 | selector: row => row.year, 28 | sortable: true, 29 | sortField: 'year', 30 | }, 31 | ]; 32 | 33 | function MyComponent() { 34 | const [items, setData] = useState(data); 35 | 36 | const handleSort = async (column, sortDirection) => { 37 | /// reach out to some API and get new data using or sortField and sortDirection 38 | // e.g. https://api.github.com/search/repositories?q=blog&sort=${column.sortField}&order=${sortDirection} 39 | 40 | setData(remoteData); 41 | }; 42 | 43 | return ( 44 | 50 | ); 51 | } 52 | ``` 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /stories/DataTable/sorting/remote.stories.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import doc from './remote.mdx'; 3 | import { orderBy } from 'lodash'; 4 | import initData from '../../constants/sampleMovieData'; 5 | import DataTable from '../../../src/index'; 6 | 7 | const columns = [ 8 | { 9 | name: 'Title', 10 | selector: row => row.title, 11 | sortable: true, 12 | sortField: 'title', 13 | }, 14 | { 15 | name: 'Director', 16 | selector: row => row.director, 17 | sortable: true, 18 | sortField: 'director', 19 | }, 20 | { 21 | name: 'Year', 22 | selector: row => row.year, 23 | sortable: true, 24 | sortField: 'year', 25 | }, 26 | ]; 27 | 28 | export const RemoteSort = () => { 29 | const [loading, setLoading] = useState(false); 30 | const [data, setData] = useState(initData); 31 | 32 | const handleSort = (column, sortDirection) => { 33 | // simulate server sort 34 | console.log(column, sortDirection); 35 | setLoading(true); 36 | 37 | // instead of setTimeout this is where you would handle your API call. 38 | setTimeout(() => { 39 | setData(orderBy(data, column.sortField, sortDirection)); 40 | setLoading(false); 41 | }, 100); 42 | }; 43 | 44 | return ( 45 | 55 | ); 56 | }; 57 | 58 | export default { 59 | title: 'Sorting/Remote Sort', 60 | component: RemoteSort, 61 | parameters: { 62 | docs: { 63 | page: doc, 64 | }, 65 | }, 66 | }; 67 | -------------------------------------------------------------------------------- /stories/DataTable/sorting/sortFunction.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Custom Sorting - sortFunction 4 | 5 | By default RDT uses `Array.sort`, however, if you wish to override the internal sorting you can instead pass in your own sorting function into the `sortFunction` property. The callback signature will give you access to RDT's internal sorting properties. Here is an example using plain old `Array.sort`; 6 | 7 | - `rows` your data rows 8 | - `selector` is the function you defined on your column.selector. We're going to call this function and pass in the row data in the example below. 9 | - `direction` RDT's current sorting position. 10 | 11 | ```js 12 | const customSort = (rows, selector, direction) => { 13 | return rows.sort((rowA, rowB) => { 14 | // use the selector function to resolve your field names by passing the sort comparitors 15 | const aField = selector(rowA) 16 | const bField = selector(rowB) 17 | 18 | let comparison = 0; 19 | 20 | if (aField > bField) { 21 | comparison = 1; 22 | } else if (aField < bField) { 23 | comparison = -1; 24 | } 25 | 26 | return direction === 'desc' ? comparison * -1 : comparison; 27 | }); 28 | }; 29 | 30 | ... 31 | 32 | ``` 33 | 34 | Or maybe you prefer to use something like lodash orderBy: 35 | 36 | ```js 37 | const customSort = (rows, selector, direction) => { 38 | return orderBy(rows, selector, direction); 39 | }; 40 | 41 | ... 42 | 43 | ``` 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /stories/DataTable/sorting/sortFunction.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import doc from './sortFunction.mdx'; 3 | import data from '../../constants/sampleMovieData'; 4 | import DataTable from '../../../src/index'; 5 | 6 | const customSort = (rows, selector, direction) => { 7 | return rows.sort((a, b) => { 8 | // use the selector to resolve your field names by passing the sort comparators 9 | const aField = selector(a).toLowerCase(); 10 | const bField = selector(b).toLowerCase(); 11 | 12 | let comparison = 0; 13 | 14 | if (aField > bField) { 15 | comparison = 1; 16 | } else if (aField < bField) { 17 | comparison = -1; 18 | } 19 | 20 | return direction === 'desc' ? comparison * -1 : comparison; 21 | }); 22 | }; 23 | 24 | const columns = [ 25 | { 26 | name: 'Title', 27 | selector: row => row.title, 28 | sortable: true, 29 | }, 30 | { 31 | name: 'Director', 32 | selector: row => row.director, 33 | sortable: true, 34 | }, 35 | { 36 | name: 'Year', 37 | selector: row => row.year, 38 | sortable: true, 39 | }, 40 | ]; 41 | 42 | export const CustomSort = () => { 43 | return ; 44 | }; 45 | 46 | export default { 47 | title: 'Sorting/Custom Sort', 48 | component: CustomSort, 49 | parameters: { 50 | docs: { 51 | page: doc, 52 | }, 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /stories/DataTable/sorting/sortFunctionCol.mdx: -------------------------------------------------------------------------------- 1 | import { Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | # Custom Sorting Individual Columns - sortFunction 4 | 5 | If you only need to override the sorting behavior for a specific column(s). In this case, the custom sorting function takes only provides you access to two arguments, rowA and rowB. 6 | 7 | ```js 8 | const caseInsensitiveSort = (rowA, rowB) => { 9 | const a = rowA.title.toLowerCase(); 10 | const b = rowB.title.toLowerCase(); 11 | 12 | if (a > b) { 13 | return 1; 14 | } 15 | 16 | if (b > a) { 17 | return -1; 18 | } 19 | 20 | return 0; 21 | }; 22 | 23 | const columns = [ 24 | { 25 | name: 'Quantity', 26 | selector: 'quantity', 27 | sortable: true, 28 | sortFunction: caseInsensitiveSort 29 | } 30 | ]; 31 | 32 | ... 33 | 34 | ``` 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /stories/DataTable/sorting/sortFunctionCol.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import doc from './sortFunctionCol.mdx'; 3 | import data from '../../constants/sampleMovieData'; 4 | import DataTable from '../../../src/index'; 5 | 6 | const caseInsensitiveSort = (rowA, rowB) => { 7 | const a = rowA.title.toLowerCase(); 8 | const b = rowB.title.toLowerCase(); 9 | 10 | if (a > b) { 11 | return 1; 12 | } 13 | 14 | if (b > a) { 15 | return -1; 16 | } 17 | 18 | return 0; 19 | }; 20 | 21 | const columns = [ 22 | { 23 | name: 'Title', 24 | selector: row => row.title, 25 | sortable: true, 26 | sortFunction: caseInsensitiveSort, 27 | }, 28 | { 29 | name: 'Director', 30 | selector: row => row.director, 31 | sortable: true, 32 | }, 33 | { 34 | name: 'Year', 35 | selector: row => row.year, 36 | sortable: true, 37 | }, 38 | ]; 39 | 40 | export const CustomColumnSort = () => { 41 | return ; 42 | }; 43 | 44 | export default { 45 | title: 'Sorting/Custom Column Sort', 46 | component: CustomColumnSort, 47 | parameters: { 48 | docs: { 49 | page: doc, 50 | }, 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /stories/coc.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | 4 | 5 | # Contributor Covenant Code of Conduct 6 | 7 | ## Our Pledge 8 | 9 | In the interest of fostering an open and welcoming environment, we as 10 | contributors and maintainers pledge to making participation in our project and 11 | our community a harassment-free experience for everyone, regardless of age, body 12 | size, disability, ethnicity, gender identity and expression, level of experience, 13 | nationality, personal appearance, race, religion, or sexual identity and 14 | orientation. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to creating a positive environment 19 | include: 20 | 21 | - Using welcoming and inclusive language 22 | - Being respectful of differing viewpoints and experiences 23 | - Gracefully accepting constructive criticism 24 | - Focusing on what is best for the community 25 | - Showing empathy towards other community members 26 | 27 | Examples of unacceptable behavior by participants include: 28 | 29 | - The use of sexualized language or imagery and unwelcome sexual attention or 30 | advances 31 | - Trolling, insulting/derogatory comments, and personal or political attacks 32 | - Public or private harassment 33 | - Publishing others' private information, such as a physical or electronic 34 | address, without explicit permission 35 | - Other conduct which could reasonably be considered inappropriate in a 36 | professional setting 37 | 38 | ## Our Responsibilities 39 | 40 | Project maintainers are responsible for clarifying the standards of acceptable 41 | behavior and are expected to take appropriate and fair corrective action in 42 | response to any instances of unacceptable behavior. 43 | 44 | Project maintainers have the right and responsibility to remove, edit, or 45 | reject comments, commits, code, wiki edits, issues, and other contributions 46 | that are not aligned to this Code of Conduct, or to ban temporarily or 47 | permanently any contributor for other behaviors that they deem inappropriate, 48 | threatening, offensive, or harmful. 49 | 50 | ## Scope 51 | 52 | This Code of Conduct applies both within project spaces and in public spaces 53 | when an individual is representing the project or its community. Examples of 54 | representing a project or community include using an official project e-mail 55 | address, posting via an official social media account, or acting as an appointed 56 | representative at an online or offline event. Representation of a project may be 57 | further defined and clarified by project maintainers. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported by contacting the project team at johnnyazee@gmail.com. All 63 | complaints will be reviewed and investigated and will result in a response that 64 | is deemed necessary and appropriate to the circumstances. The project team is 65 | obligated to maintain confidentiality with regard to the reporter of an incident. 66 | Further details of specific enforcement policies may be posted separately. 67 | 68 | Project maintainers who do not follow or enforce the Code of Conduct in good 69 | faith may face temporary or permanent repercussions as determined by other 70 | members of the project's leadership. 71 | 72 | ## Attribution 73 | 74 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 75 | available at [http://contributor-covenant.org/version/1/4][version] 76 | 77 | [homepage]: http://contributor-covenant.org 78 | [version]: http://contributor-covenant.org/version/1/4/ 79 | -------------------------------------------------------------------------------- /stories/constants/sampleDesserts.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | id: 1, 4 | name: 'Frozen yogurt', 5 | type: 'Ice cream', 6 | calories: 159, 7 | fat: 6.0, 8 | carbs: 24, 9 | protein: 4.0, 10 | sodium: 87, 11 | calcium: 14, 12 | iron: 1, 13 | }, 14 | { 15 | id: 2, 16 | name: 'Ice cream sandwhich', 17 | type: 'Ice cream', 18 | calories: 237, 19 | fat: 9.0, 20 | carbs: 37, 21 | protein: 4.3, 22 | sodium: 129, 23 | calcium: 8, 24 | iron: 1, 25 | }, 26 | { 27 | id: 3, 28 | name: 'Eclair', 29 | type: 'Pastry', 30 | calories: 262, 31 | fat: 16.0, 32 | carbs: 37, 33 | protein: 6.0, 34 | sodium: 337, 35 | calcium: 6, 36 | iron: 7, 37 | }, 38 | { 39 | id: 4, 40 | name: 'Cupcake', 41 | type: 'Pastry', 42 | calories: 305, 43 | fat: 3.7, 44 | carbs: 67, 45 | protein: 4.3, 46 | sodium: 413, 47 | calcium: 3, 48 | iron: 8, 49 | }, 50 | { 51 | id: 5, 52 | name: 'Gingerbread', 53 | type: 'Pastry', 54 | calories: 356, 55 | fat: 16.0, 56 | carbs: 49, 57 | protein: 3.9, 58 | sodium: 327, 59 | calcium: 7, 60 | iron: 16, 61 | }, 62 | { 63 | id: 6, 64 | name: 'Jelly bean', 65 | type: 'Other', 66 | calories: 375, 67 | fat: 0.0, 68 | carbs: 94, 69 | protein: 0.0, 70 | sodium: 50, 71 | calcium: 0, 72 | iron: 0, 73 | }, 74 | { 75 | id: 7, 76 | name: 'Lollipop', 77 | type: 'Other', 78 | calories: 392, 79 | fat: 0.2, 80 | carbs: 98, 81 | protein: 0.0, 82 | sodium: 38, 83 | calcium: 0, 84 | iron: 2, 85 | }, 86 | { 87 | id: 8, 88 | name: 'Honeycomb', 89 | type: 'Other', 90 | calories: 408, 91 | fat: 3.2, 92 | carbs: 87, 93 | protein: 6.5, 94 | sodium: 562, 95 | calcium: 0, 96 | iron: 45, 97 | }, 98 | { 99 | id: 9, 100 | name: 'Donut', 101 | type: 'Pastry', 102 | calories: 52, 103 | fat: 25.0, 104 | carbs: 51, 105 | protein: 4.9, 106 | sodium: 326, 107 | calcium: 2, 108 | iron: 22, 109 | }, 110 | { 111 | id: 10, 112 | name: 'KitKat', 113 | type: 'Other', 114 | calories: 16, 115 | fat: 6.0, 116 | carbs: 65, 117 | protein: 7.0, 118 | sodium: 54, 119 | calcium: 12, 120 | iron: 6, 121 | }, 122 | ]; 123 | -------------------------------------------------------------------------------- /stories/cssEscape.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs'; 2 | 3 | 4 | 5 | ## CSS Escape Hatch 6 | 7 | If you would like to customize the layout components of React Data Table using styled-components (e.g. `styled(DataTable)`), or your favorite CSS, SCSS, LESS, etc.., pre-processor, or you simply need an escape hatch you can utilize the following classNames: 8 | 9 | - rdt_Table 10 | - rdt_TableRow 11 | - rdt_TableCol 12 | - rdt_TableCol_Sortable 13 | - rdt_TableCell 14 | - rdt_TableHeader 15 | - rdt_TableFooter 16 | - rdt_TableHead 17 | - rdt_TableHeadRow 18 | - rdt_TableBody 19 | - rdt_ExpanderRow 20 | -------------------------------------------------------------------------------- /stories/customStyles.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs'; 2 | 3 | 4 | 5 | # Overidding Styling Using css-in-js with customStyles 6 | 7 | For more advanced use cases you can override or replace the global default styling using the `customStyles` prop and passing in your own css-in-js. 8 | 9 | **Disclaimer**: you're on your own here since you will have the power to not only customize but break RDT. 10 | 11 | Let's apply a simple `customStyles` to override the default row height and change the cell padding: 12 | 13 | ```js 14 | import DataTable from 'react-data-table-component'; 15 | 16 | // Internally, customStyles will deep merges your customStyles with the default styling. 17 | const customStyles = { 18 | rows: { 19 | style: { 20 | minHeight: '72px', // override the row height 21 | }, 22 | }, 23 | headCells: { 24 | style: { 25 | paddingLeft: '8px', // override the cell padding for head cells 26 | paddingRight: '8px', 27 | }, 28 | }, 29 | cells: { 30 | style: { 31 | paddingLeft: '8px', // override the cell padding for data cells 32 | paddingRight: '8px', 33 | }, 34 | }, 35 | }; 36 | 37 | const MyComponent = () => ; 38 | ``` 39 | 40 | View [styles.ts](https://github.com/jbetancur/react-data-table-component/blob/master/src/DataTable/styles.ts) for a detailed catalog of RDT styles that you can override or extend using css-in-js objects. 41 | -------------------------------------------------------------------------------- /stories/development.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs'; 2 | 3 | 4 | 5 | # Development 6 | 7 | ## Setup 8 | 9 | Install the latest [Node JS LTS](https://nodejs.org/) and [Yarn](https://yarnpkg.com/getting-started/install) and simply run `yarn` or `yarn install` command in the root and stories directory. 10 | 11 | > It is advised to run the script whenever NPM packages are installed. 12 | 13 | ## Local development 14 | 15 | During development: 16 | 17 | ```sh 18 | # watch and build new source changes 19 | yarn start 20 | # or serve *.stories.js files and manually test on the Storybook app 21 | yarn storybook 22 | ``` 23 | 24 | ## Including NPM packages 25 | 26 | This project uses two package.json structure 27 | 28 | ### Library dependencies 29 | 30 | ```sh 31 | yarn add [package-name] --dev # for dev tools 32 | yarn add [package-name] # for app 33 | ``` 34 | 35 | ## Lint 36 | 37 | ```sh 38 | yarn lint # runs linter to detect any style issues (css & js) 39 | yarn lint:css # lint only css 40 | yarn lint:js # lint only js 41 | yarn lint:js --fix # tries to fix js lint issues 42 | ``` 43 | 44 | ## Test 45 | 46 | ```sh 47 | yarn test:tdd # runs functional/unit tests using Jest with watcher 48 | yarn test # runs functional/unit tests using Jest 49 | yarn test --coverage # with coverage 50 | ``` 51 | 52 | ## Build 53 | 54 | ```sh 55 | yarn build # builds sources at src/ 56 | ``` 57 | -------------------------------------------------------------------------------- /stories/enums.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs'; 2 | 3 | 4 | 5 | # Enumerators 6 | 7 | React Data Table provides you with enums instead of using strings for various properties. These are optional with JavaScript but required when using a TypeScript project. In any case, use the enums when you can. 8 | 9 | ## Direction 10 | 11 | - Direction.LTR = 'ltr' 12 | - Direction.RTL = 'rtl' 13 | - Direction.AUTO = 'auto' 14 | 15 | ```js 16 | import DataTable { Direction } from 'react-data-table-Component'; 17 | 18 | 19 | ``` 20 | 21 | ## Alignment 22 | 23 | - LEFT = 'left' 24 | - RIGHT = 'right' 25 | - CENTER = 'center' 26 | 27 | ```js 28 | import DataTable { Alignment } from 'react-data-table-Component'; 29 | 30 | 31 | ``` 32 | 33 | ## Media 34 | 35 | - SM = 'sm' 36 | - MD = 'md' 37 | - LG = 'lg' 38 | 39 | ```js 40 | import DataTable { Media } from 'react-data-table-Component'; 41 | 42 | const columns = [ 43 | { 44 | name: 'Title', 45 | selector: row => row.title, 46 | hide: Media.SM 47 | } 48 | ]; 49 | ``` 50 | -------------------------------------------------------------------------------- /stories/headers/fixed.stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import data from '../constants/sampleMovieData'; 3 | import DataTable from '../../src/index'; 4 | 5 | const columns = [ 6 | { 7 | name: 'Title', 8 | selector: row => row.title, 9 | sortable: true, 10 | reorder: true, 11 | }, 12 | { 13 | name: 'Director', 14 | selector: row => row.director, 15 | sortable: true, 16 | reorder: true, 17 | }, 18 | { 19 | name: 'Year', 20 | selector: row => row.year, 21 | sortable: true, 22 | reorder: true, 23 | }, 24 | ]; 25 | 26 | const FixedHeaderStory = ({ fixedHeader, fixedHeaderScrollHeight }) => ( 27 | 35 | ); 36 | 37 | const Template = args => ; 38 | 39 | export const FixedHeader = Template.bind({}); 40 | 41 | FixedHeader.args = { 42 | fixedHeader: true, 43 | fixedHeaderScrollHeight: '300px', 44 | }; 45 | 46 | export default { 47 | title: 'Headers/Fixed Header', 48 | component: FixedHeader, 49 | }; 50 | -------------------------------------------------------------------------------- /stories/installation.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | 4 | 5 | # Requirements 6 | 7 | React Data Table Component requires the following be installed in your project: 8 | 9 | - React 16.8.0+ 10 | - styled-components 3.2.3+ || 4.0.0+ || 5.0.0+ 11 | 12 | 13 | # Installation 14 | 15 | ## If you already have styled-components installed 16 | 17 | React Data Table utilizes the wonderful `styled-components` library. If you've already installed `styled-components` there is no need to install it again as long as the version meets the requirements section above. 18 | 19 | ### npm 20 | 21 | ```sh 22 | npm install react-data-table-component 23 | ``` 24 | 25 | ### yarn 26 | 27 | ```sh 28 | yarn add react-data-table-component 29 | ``` 30 | 31 | ## If you DO NOT already have styled-components installed 32 | 33 | ### npm 34 | 35 | ```sh 36 | npm install react-data-table-component styled-components 37 | ``` 38 | 39 | ### yarn 40 | 41 | ```sh 42 | yarn add react-data-table-component styled-components 43 | ``` -------------------------------------------------------------------------------- /stories/intro.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | 4 | 5 | [![Netlify Status](https://api.netlify.com/api/v1/badges/26e0d16d-a986-46b1-9097-1a76c10d7cad/deploy-status)](https://app.netlify.com/sites/react-data-table-component/deploys) [![npm version](https://badge.fury.io/js/react-data-table-component.svg)](https://badge.fury.io/js/react-data-table-component) [![codecov](https://codecov.io/gh/jbetancur/react-data-table-component/branch/master/graph/badge.svg)](https://codecov.io/gh/jbetancur/react-data-table-component) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 6 | 7 | # React Data Table Component 8 | 9 | [![GitHub release](https://img.shields.io/github/release/jbetancur/react-data-table-component.svg)](https://GitHub.com/jbetancur/react-data-table-component/releases/) [![GitHub stars](https://img.shields.io/github/stars/jbetancur/react-data-table-component.svg?style=social&label=Star&maxAge=2592000)](https://GitHub.com/jbetancur/react-data-table-component/stargazers/) 10 | 11 | Creating yet another React table library came out of necessity while developing a web application for a growing startup. I discovered that while there are some great table libraries out there, some required heavy customization, were missing out of the box features such as built in sorting and pagination, or required understanding the atomic structure of html tables. 12 | 13 | If you want to achieve balance with the force and want a simple but flexible table library give React Data Table Component a chance. If you require an Excel clone, then this is not the React table library you are looking for 👋 14 | 15 | # Key Features 16 | 17 | - Declarative configuration 18 | - Built-in and configurable: 19 | - Sorting 20 | - Selectable Rows 21 | - Expandable Rows 22 | - Pagination 23 | - Themeable/Customizable 24 | - Accessibility 25 | - Responsive (via x-scroll/flex) 26 | 27 | # Supporting React Data Table Component 28 | 29 | If you would like to support the project financially, visit 30 | [our campaign on OpenCollective](https://opencollective.com/react-data-table-component). Your contributions help accelerate the development of React Data Table Component! 31 | 32 | 33 | 34 | 35 | 36 | # Interested in Contributing? 37 | 38 | 1. Read the [Code of Conduct](/docs/getting-started-coc--page) 39 | 2. Read the [Development Process](/docs/development--page) 40 | 3. Read the [Logging Issues & Features](/docs/getting-started-issues--page) 41 | 42 | # Contributors 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /stories/issues.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | 4 | 5 | # Logging Issues 6 | 7 | **Issues or features with insufficient detail will most likely be ignored/unanswered**. 8 | 9 | I'm excited about this project and I want to help you, but my time is also valuable and I'm not a mind reader. **Please follow the issue templates and provide as much detail as possible.** 10 | 11 | For logging issues/bugs please use the [issue template](https://github.com/jbetancur/react-data-table-component/tree/master/.github/ISSUE_TEMPLATE). 12 | 13 | It's helpful to know the versions, steps to reproduce, and it's **especially nice to have a [codesandbox](https://codesandbox.io/s/react-data-table-sandbox-ccyuu)** when possible or at least your component code. The more details you provide me the faster we can get a fix as needed. 14 | 15 | While I absolutely ❤️ ❤️ ❤️ to teach React I simply do not have the bandwidth for that in this forum to help you understand how React State works, so **please keep the focus on React Data Table Component** Issues/Features. 16 | 17 | # Features 18 | 19 | When submitting a feature please make sure that you put detailed thought into the design. Please provide some use cases, examples, pros/cons and even better **start a converstaion about it**. 20 | Consult UX best practices (I tend to stick to material design principles for UX descisions). 21 | 22 | Convince me/users of this library that your awesome new feature will benefit all users of this library. 23 | 24 | # Pull Requests 25 | 26 | First off, if you are submitting a PR that's awesome! PR's are encouraged! But, just becuase you submit a PR does not mean it will be merged if it does not fit with the design or you are unresponsive to feedback. 27 | 28 | If you have an idea for a PR that is a little more complex, then it **should be a feature request first** that can perhaps be discussed prior to implementation so you don't waste your time working on a PR that may never get merged. 29 | 30 | Aside from implementation details, a lack of detailed explanation, improper linting/formatting, or missing/invalid tests will result in a PR being declined. 31 | 32 | # Housekeeping Bot 33 | 34 | Stale Issues, Features, PR's will auto self destruct (close) after 30 days... 35 | -------------------------------------------------------------------------------- /stories/libraryIntegration.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs'; 2 | 3 | 4 | 5 | # UI Library Integration 6 | 7 | React Data Table Component makes it easy to incorporate ui components from other libraries for overriding things like the sort icon, select checkbox. 8 | 9 | - [MaterialUI 5](https://codesandbox.io/s/react-data-table-materialui-72gdo) 10 | - [Bootstrap 5](https://codesandbox.io/s/react-data-table-sandbox-z6gtg) 11 | -------------------------------------------------------------------------------- /stories/patterns.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | 4 | 5 | # Patterns 6 | 7 | ## Common DataTable Component 8 | 9 | ### JavaScript 10 | 11 | If you use React Data Table across your application and you are customizing or using custom components often you'll find you are repeating alot of code. 12 | 13 | The following pattern allows us to create a re-usable `DataTable` with our defaults baked in. In this case, we want to ensure all our tables are `dense` and have `pagination`, and finally let's override React Data Tables components with some material-ui sexiness: 14 | 15 | ```js 16 | import React from 'react'; 17 | import DataTable from 'react-data-table-component'; 18 | import Checkbox from '@material-ui/core/Checkbox'; 19 | 20 | import ArrowDownward from '@material-ui/icons/ArrowDownward'; 21 | 22 | const sortIcon = ; 23 | const selectProps = { indeterminate: isIndeterminate => isIndeterminate }; 24 | 25 | function DataTableBase(props) { 26 | return ( 27 | 35 | ); 36 | } 37 | 38 | export default DataTableBase; 39 | ``` 40 | 41 | Now, instead of importing `DataTable` you would import your custom `DataTableBase` component and pass in your `columns`, `data` or whatever other React Data Tably property is needed: 42 | 43 | ```js 44 | import React from 'react'; 45 | import DataTable from '../mycomponents/DataTableBase'; 46 | 47 | ... 48 | 49 | function MyComponent() { 50 | return ; 51 | } 52 | 53 | export default MyComponent; 54 | ``` 55 | 56 | ### TypeScript 57 | 58 | We can do the same in TypeScript: 59 | 60 | ```ts 61 | import React from 'react'; 62 | import DataTable, { TableProps } from '../../src/index'; 63 | import Checkbox from '@material-ui/core/Checkbox'; 64 | import ArrowDownward from '@material-ui/icons/ArrowDownward'; 65 | 66 | const sortIcon = ; 67 | const selectProps = { indeterminate: (isIndeterminate: boolean) => isIndeterminate }; 68 | 69 | function DataTableBase(props: TableProps): JSX.Element { 70 | return ( 71 | 79 | ); 80 | } 81 | 82 | export default DataTableBase; 83 | ``` 84 | 85 | Usage: 86 | 87 | ```ts 88 | import React from 'react'; 89 | import DataTable from '../mycomponents/DataTableBase'; 90 | 91 | ... 92 | 93 | function MyComponent() { 94 | return ; 95 | } 96 | 97 | export default MyComponent; 98 | ``` 99 | -------------------------------------------------------------------------------- /stories/reporting.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs'; 2 | 3 | 4 | 5 | # Stargazers over time 6 | 7 | [![Stargazers over time](https://starchart.cc/jbetancur/react-data-table-component.svg)](https://starchart.cc/jbetancur/react-data-table-component) 8 | -------------------------------------------------------------------------------- /stories/shared/Button.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const ButtonStyle = styled.button` 5 | background-color: #2979ff; 6 | border: none; 7 | color: white; 8 | padding: 8px 32px 8px 32px; 9 | text-align: center; 10 | text-decoration: none; 11 | display: inline-block; 12 | font-size: 16px; 13 | border-radius: 3px; 14 | 15 | &:hover { 16 | cursor: pointer; 17 | } 18 | `; 19 | 20 | // eslint-disable-next-line react/prop-types 21 | export default ({ children, ...rest }) => {children}; 22 | -------------------------------------------------------------------------------- /stories/shared/CustomMaterialMenu.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import IconButton from '@material-ui/core/IconButton'; 3 | import Menu from '@material-ui/core/Menu'; 4 | import MenuItem from '@material-ui/core/MenuItem'; 5 | import ListItemIcon from '@material-ui/core/ListItemIcon'; 6 | import MoreVertIcon from '@material-ui/icons/MoreVert'; 7 | import DeleteIcon from '@material-ui/icons/Delete'; 8 | import Divider from '@material-ui/core/Divider'; 9 | import Typography from '@material-ui/core/Typography'; 10 | 11 | // eslint-disable-next-line react/prop-types 12 | export default ({ row, onDeleteRow, size }) => { 13 | const [anchorEl, setAnchorEl] = React.useState(null); 14 | 15 | const handleClick = event => { 16 | setAnchorEl(event.currentTarget); 17 | }; 18 | 19 | const handleClose = () => { 20 | setAnchorEl(null); 21 | }; 22 | const deleteRow = () => { 23 | if (onDeleteRow) { 24 | onDeleteRow(row); 25 | } 26 | }; 27 | 28 | return ( 29 |
30 | 31 | 32 | 33 | 49 | Item One 50 | 51 | Item Two 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Delete 60 | 61 | 62 |
63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /stories/shared/users.js: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | 3 | const createUser = () => ({ 4 | id: faker.string.uuid(), 5 | name: faker.internet.userName(), 6 | email: faker.internet.email(), 7 | address: faker.location.streetAddress(), 8 | bio: faker.lorem.sentence(), 9 | image: faker.image.avatar(), 10 | }); 11 | 12 | const createUsers = (numUsers = 5) => new Array(numUsers).fill(undefined).map(createUser); 13 | 14 | export default createUsers(20); 15 | -------------------------------------------------------------------------------- /stories/simpleExamples.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | 4 | 5 | ## Basic 6 | 7 | Creating a React Data Table should be easy. Simply define your columns and data arrays: 8 | 9 | ```js 10 | import DataTable from 'react-data-table-component'; 11 | 12 | const columns = [ 13 | { 14 | name: 'Title', 15 | selector: row => row.title, 16 | }, 17 | { 18 | name: 'Year', 19 | selector: row => row.year, 20 | }, 21 | ]; 22 | 23 | const data = [ 24 | { 25 | id: 1, 26 | title: 'Beetlejuice', 27 | year: '1988', 28 | }, 29 | { 30 | id: 2, 31 | title: 'Ghostbusters', 32 | year: '1984', 33 | }, 34 | ] 35 | 36 | function MyComponent() { 37 | return ( 38 | 42 | ); 43 | }; 44 | ``` 45 | 46 | ## Basic Sorting 47 | 48 | Adding sorting a table has never been easier: 49 | 50 | ```js 51 | import DataTable from 'react-data-table-component'; 52 | 53 | const columns = [ 54 | { 55 | name: 'Title', 56 | selector: row => row.title, 57 | sortable: true, 58 | }, 59 | { 60 | name: 'Year', 61 | selector: row => row.year, 62 | sortable: true, 63 | }, 64 | ]; 65 | 66 | const data = [ 67 | { 68 | id: 1, 69 | title: 'Beetlejuice', 70 | year: '1988', 71 | }, 72 | { 73 | id: 2, 74 | title: 'Ghostbusters', 75 | year: '1984', 76 | }, 77 | ] 78 | 79 | function MyComponent() { 80 | return ( 81 | 85 | ); 86 | }; 87 | ``` 88 | 89 | ## Selectable Rows 90 | 91 | Adding selectable rows is usually quite a bit of code. Let's simplify: 92 | 93 | ```js 94 | import DataTable from 'react-data-table-component'; 95 | 96 | const columns = [ 97 | { 98 | name: 'Title', 99 | selector: row => row.title, 100 | }, 101 | { 102 | name: 'Year', 103 | selector: row => row.year, 104 | }, 105 | ]; 106 | 107 | const data = [ 108 | { 109 | id: 1, 110 | title: 'Beetlejuice', 111 | year: '1988', 112 | }, 113 | { 114 | id: 2, 115 | title: 'Ghostbusters', 116 | year: '1984', 117 | }, 118 | ] 119 | 120 | function MyComponent() { 121 | return ( 122 | 127 | ); 128 | }; 129 | ``` 130 | 131 | ## Expandable Rows 132 | 133 | Adding expandable rows is no easy feat. Let's simplify: 134 | 135 | ```js 136 | import DataTable from 'react-data-table-component'; 137 | 138 | // A super simple expandable component. 139 | const ExpandedComponent = ({ data }) =>
{JSON.stringify(data, null, 2)}
; 140 | 141 | const columns = [ 142 | { 143 | name: 'Title', 144 | selector: row => row.title, 145 | }, 146 | { 147 | name: 'Year', 148 | selector: row => row.year, 149 | }, 150 | ]; 151 | 152 | const data = [ 153 | { 154 | id: 1, 155 | title: 'Beetlejuice', 156 | year: '1988', 157 | }, 158 | { 159 | id: 2, 160 | title: 'Ghostbusters', 161 | year: '1984', 162 | }, 163 | ] 164 | 165 | function MyComponent() { 166 | return ( 167 | 173 | ); 174 | }; 175 | ``` 176 | -------------------------------------------------------------------------------- /stories/theming.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs'; 2 | 3 | 4 | 5 | # Defining Your Own Theme 6 | 7 | By default RDT comes with a `light` and `dark` theme that can be set on the `theme` property, however, you can create your very own theme using the `createTheme` helper or tweak the existing built in `light` and `dark` themes. 8 | 9 | Refer to [themes.ts](https://github.com/jbetancur/react-data-table-component/blob/master/src/DataTable/themes.ts) for properties you can use to create your own color theme. 10 | 11 | # Custom Solarized Theme 12 | 13 | Let's create a `solarized` theme that inherits from the build in `dark` theme: 14 | 15 | ```js 16 | import DataTable, { createTheme } from 'react-data-table-component'; 17 | 18 | // createTheme creates a new theme named solarized that overrides the build in dark theme 19 | createTheme('solarized', { 20 | text: { 21 | primary: '#268bd2', 22 | secondary: '#2aa198', 23 | }, 24 | background: { 25 | default: '#002b36', 26 | }, 27 | context: { 28 | background: '#cb4b16', 29 | text: '#FFFFFF', 30 | }, 31 | divider: { 32 | default: '#073642', 33 | }, 34 | action: { 35 | button: 'rgba(0,0,0,.54)', 36 | hover: 'rgba(0,0,0,.08)', 37 | disabled: 'rgba(0,0,0,.12)', 38 | }, 39 | }, 'dark'); 40 | 41 | const MyComponent = () => ( 42 | 47 | ); 48 | ``` 49 | 50 | ## Tweaking the built in light and dark themes 51 | 52 | You can also take an existing theme and tweak the colors without much effort: 53 | 54 | ```js 55 | import DataTable, { createTheme } from 'react-data-table-component'; 56 | 57 | // createTheme creates a new theme named solarized that overrides the build in dark theme 58 | createTheme('dark', { 59 | background: { 60 | default: 'transparent', 61 | }, 62 | }); 63 | 64 | const MyComponent = () => ( 65 | 70 | ); 71 | ``` 72 | 73 | Once you've created your theme it will now be available to all DataTables across your project so you may want to define your custom themes in seperate files. 74 | -------------------------------------------------------------------------------- /stories/typescript.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story, Canvas } from '@storybook/addon-docs'; 2 | 3 | 4 | 5 | # TypeScript 6 | 7 | React Data Table is built with TypeScript so typings are buit in. First, we'll need to define our data type (or interface): 8 | 9 | ```ts 10 | import DataTable, { TableColumn } from 'react-data-table-component'; 11 | 12 | interface DataRow { 13 | title: string; 14 | director: string; 15 | year: string; 16 | } 17 | ``` 18 | 19 | Alternatively, if you want to define `DataRow` as a type: 20 | 21 | ```ts 22 | import DataTable, { TableColumn } from 'react-data-table-component'; 23 | 24 | type DataRow = { 25 | title: string; 26 | director: string; 27 | year: string; 28 | }; 29 | ``` 30 | 31 | Next, we'll create our columns definitions. `TableColumn` is an interface representing a column definition. 32 | `TableColumn` takes a generic parameter ``. By passing `TableColumn[]` (columns is an array) we now have access to our `dataRow` props on any property in our table `columns` that accesses our data: 33 | 34 | ```ts 35 | const columns: TableColumn[] = [ 36 | { 37 | name: 'Title', 38 | selector: row => row.title, 39 | }, 40 | { 41 | name: 'Director', 42 | selector: row => row.director, 43 | }, 44 | { 45 | name: 'Year', 46 | selector: row => row.year, 47 | }, 48 | ]; 49 | ``` 50 | 51 | Finally, we'll implement our TypeScript component that uses `DataTable`: 52 | 53 | ```ts 54 | function MyComponent(): JSX.Element { 55 | return ; 56 | } 57 | ``` 58 | 59 | Or, if you prefer using `React.FC`: 60 | 61 | ```ts 62 | const MyComponent: React.FC = () => { 63 | return ; 64 | }; 65 | ``` 66 | 67 | Putting it all together: 68 | 69 | ```ts 70 | import DataTable, { TableColumn } from 'react-data-table-component'; 71 | 72 | interface DataRow { 73 | title: string; 74 | director: string; 75 | year: string; 76 | } 77 | 78 | const columns: TableColumn[] = [ 79 | { 80 | name: 'Title', 81 | selector: row => row.title, 82 | }, 83 | { 84 | name: 'Director', 85 | selector: row => row.director, 86 | }, 87 | { 88 | name: 'Year', 89 | selector: row => row.year, 90 | }, 91 | ]; 92 | 93 | function MyComponent(): JSX.Element { 94 | return ; 95 | } 96 | 97 | export default MyComponent; 98 | ``` 99 | 100 | ## Types 101 | 102 | In addition to `TableColumn` we can also access various other React Data Table types. 103 | 104 | The following are going to be the most commonly used and accept a generic `T` parameter, which will always be your data's type or interface: 105 | 106 | - `TableColumn` 107 | - `TableProps` 108 | - `ConditionalStyles` 109 | - `ExpanderComponentProps` 110 | - `PaginationComponentProps` 111 | 112 | The following types are available for advanced or niche use cases: 113 | 114 | - `TableRow` 115 | - `TableStyles` 116 | - `Theme` 117 | - `Themes` 118 | - `PaginationOptions` 119 | - `PaginationServerOptions` 120 | - `ContextMessage` 121 | - `SortOrder` 122 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "esModuleInterop": true, 5 | "experimentalDecorators": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "isolatedModules": true, 8 | "noEmit": true, 9 | "noImplicitReturns": true, 10 | "noUnusedLocals": true, 11 | "removeComments": true, 12 | "resolveJsonModule": true, 13 | "skipLibCheck": true, 14 | "strict": true, 15 | "sourceMap": true, 16 | "baseUrl": "src", 17 | "jsx": "react", 18 | "moduleResolution": "node", 19 | "outDir": "dist", 20 | "target": "es6", 21 | "module": "esnext", 22 | "lib": [ 23 | "es6", 24 | "es7", 25 | "dom" 26 | ], 27 | "declaration": true 28 | }, 29 | "include": [ 30 | "src/**/*" 31 | ], 32 | "exclude": [ 33 | "node_modules", 34 | "dist", 35 | "src/**/__tests__", 36 | "src/**/internal" 37 | ] 38 | } 39 | --------------------------------------------------------------------------------