diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..486e876c --- /dev/null +++ b/.babelrc @@ -0,0 +1,9 @@ +{ + "presets": [ + "@babel/preset-env", + "@babel/preset-react" + ], + "plugins": [ + "@babel/plugin-proposal-optional-chaining" + ] +} diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..1fd61fc5 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +src/components/SandBox.jsx +src/components/SandBoxComponents/ \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..a3b80303 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,69 @@ +module.exports = { + parser: 'babel-eslint', // use babel to parse + env: { + browser: true, + es6: true, + node: true, // for node globals, such as 'module' + jest: true, // for jest globals, such as 'test' and 'expect' + }, + // 'plugins' section only 'activate' these plugins + // still need manually edit 'rules' to actually use them + // such as { react/xxx: 'error' } + plugins: [ + 'react', + ], + // 'extends' section will extend these configs + // and they will take effect directly + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'airbnb', + 'airbnb/hooks', + 'plugin:react-hooks/recommended', + ], + rules: { + indent: [2, 2, { SwitchCase: 1 }], + quotes: [2, 'single'], + semi: [2, 'always'], + 'jsx-quotes': [2, 'prefer-single'], + 'linebreak-style': [2, 'unix'], + 'arrow-parens': [2, 'as-needed'], + 'react/jsx-curly-spacing': [2, { + when: 'always', + spacing: { objectLiterals: 'never' }, + }], + 'no-restricted-syntax': [ + 'error', + 'ForInStatement', + // 'ForOfStatement', + 'LabeledStatement', + 'WithStatement', + ], + + /* ---------- turned off ---------- */ + 'max-len': 0, + 'react/jsx-filename-extension': 0, + 'react/forbid-prop-types': 0, + 'react/require-default-props': 0, + 'no-underscore-dangle': 0, + 'no-multi-spaces': 0, + 'jsx-a11y/click-events-have-key-events': 0, // allow click handler on
+ 'jsx-a11y/no-static-element-interactions': 0, // allow click handler on
+ 'no-unused-expressions': [2, { allowShortCircuit: true }], // allow x && y() + 'import/no-extraneous-dependencies': [2, { devDependencies: true }], // so can import enzyme, which is dev dependencies + }, + + // don't know what these are... generated by eslint --init + // TODO: do more research about these + globals: { + Atomics: 'readonly', + SharedArrayBuffer: 'readonly', + }, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 2018, + sourceType: 'module', + }, +}; diff --git a/.gitignore b/.gitignore index 8706a704..588dc17a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +dist/ +notes.md + yarn-error.log npm-debug.log build diff --git a/LICENSE b/LICENSE deleted file mode 100644 index cf62dcd9..00000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 Shunji Zhan - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index a30811c1..4857f0a8 100644 --- a/README.md +++ b/README.md @@ -1,188 +1,50 @@ -# Introduction +# React Folder Tree +A powerful and customizable react treeview library. -This is a folder tree written in ReactJS. -We can do: +## Core Features +- **Half Check** (indeterminate checkboxes): when some of the children nodes are checked, parent node automatically becomes half check. +- **Inline CRUD** operations: rename nodes, create new nodes, and delete nodes. +- **Customizable**: all icons are customizable, so you can build your favorite styles, as well as custom functionalities for user interactions. -- click each carat to expand/collapse the folder -- click the checkbox to (un)check each folder and file. (un)check each folder will automatically (un)check all sub-folders, including all the files in these folders. If part of the sub-folders or files in a folder are checked, this folder will display a half check. -- click the folder/file name to select it, and it will be hightlighted in blue -- click the pencil beside the folder/file to rename it -- click the delete button to delete the selected folder/file -- click the Add button to add new file in the selected folder/file. Adding a file-2 to a file-1 will change file-1 to a folder; if all sub folder/files of a folder are deleted, this folder will become a file. The new file's check status is same as its parent +## Quick Preview +![folder-tree-demo](/assets/folder-tree-demo.gif) -# Props -- data: initial data to construct the tree. Sample data can be found [below](#sample-data) -- onChange(data): It will call this function after any of these four actions: (un)check, add, delete, or rename. Where data is the object representing the tree of all selected files/folders (filtered out all unchecked files/folders). -- FileComponent & FolderComponent: you can inject your own components here. Default file/folder component is already provided. -# Sample Tree: +## Demos & Code Examples +[===== HERE =====](https://shunjizhan.github.io/react-folder-tree-demos/) +[===== HERE =====](https://shunjizhan.github.io/react-folder-tree-demos/) +[===== HERE =====](https://shunjizhan.github.io/react-folder-tree-demos/) -![](https://raw.githubusercontent.com/shunjizhan/React-Folder-Tree/master/folder-tree-demo.gif?raw=true) +## Basic Usage & Props +```tsx +import FolderTree, { testData } from 'react-folder-tree'; -# To Install: - npm install --save react-folder-tree +const BasicTree = () => { + const onTreeStateChange = state => console.log('tree state: ', state); -# To Run: + return ( + + ); +}; - +``` -Remember to include the above link in your html page. Otherwise the icons won't show up. +| prop name | description | type | options | +|-------------------|-----------------------------------------|----------|------------------------------------------------| +| data | initial tree state data (required) | object | N/A | +| onChange | callback when tree state changes | function | console.log (default) | +| initCheckedStatus | initial check status of all nodes | string | 'unchecked' (default) \| 'checked' \| 'custom' | +| initOpenStatus | initial open status of all folder nodes | string | 'close' (default) \| 'open' \| 'custom' | +| iconComponents | custom icon components | object | N/A | +| indentPixels | ident pixels | number | 30 (default) | - import React from 'react'; - import ReactDOM from 'react-dom'; - import FolderTree from 'react-folder-tree'; +## Note +After upgrading to `v4.0`, old versions are no compatible anymore, please try out new version or specify old version when installing! - const testData = YOUR DATA; - - ReactDOM.render( - , - document.getElementById('root') - ) - -# Data Format: - - { - id: number, - filename: string, - children: array of *this* [optional] - } - -# Sample Data: - - const testData = { - "id": 1, - "filename": "All Categories", - "children": [ - { - "id": 2, - "filename": "For Sale", - "children": [ - { - "id": 3, - "filename": "Audio & Stereo", - "children": [ - { - "id": 4, - "filename": "For Sale", - "children": [ - { - "id": 5, - "filename": "Audio & Stereo", - }, - { - "id": 6, - "filename": "Baby & Kids Stuff", - }, - { - "id": 7, - "filename": "Music, Films, Books & Games", - } - ] - }, - { - "id": 8, - "filename": "Motors", - "children": [ - { - "id": 9, - "filename": "Car Parts & Accessories", - }, - { - "id": 10, - "filename": "Cars", - }, - { - "id": 11, - "filename": "Motorbike Parts & Accessories", - } - ] - }, - { - "id": 12, - "filename": "Jobs", - "children": [ - { - "id": 13, - "filename": "Accountancy", - }, - { - "id": 14, - "filename": "Financial Services & Insurance", - }, - { - "id": 15, - "filename": "Bar Staff & Management", - } - ] - } - ] - }, - { - "id": 16, - "filename": "Baby & Kids Stuff", - }, - { - "id": 17, - "filename": "Music, Films, Books & Games", - } - ] - }, - { - "id": 18, - "filename": "Motors", - "children": [ - { - "id": 19, - "filename": "Car Parts & Accessories", - }, - { - "id": 20, - "filename": "Cars", - }, - { - "id": 21, - "filename": "Motorbike Parts & Accessories", - } - ] - }, - { - "id": 22, - "filename": "Jobs", - "children": [ - { - "id": 23, - "filename": "Accountancy", - }, - { - "id": 24, - "filename": "Financial Services & Insurance", - }, - { - "id": 25, - "filename": "Bar Staff & Management", - } - ] - } - ] - } - -# Resources - -Testing data is from [here](http://codepen.io/anon/pen/Ftkln?editors=0010) - -Icons are from [here](https://www.npmjs.com/package/react-fontawesome) - -# Contribution -Any contributions are welcomed! Please upload issues or pull requests on the github page, thank you! - -# TODO -- css modules of font awesome -- in editing mode the inputbox doesn't hover automatically -- press enter to confirm (now must click the confirm icon) -- fully test all funtionalities -- change structure: since now each Treenode has path, there should exist more concise way to handle check -- remove excessive dependencies \ No newline at end of file +## Contributions +Welcome! diff --git a/assets/folder-tree-demo.gif b/assets/folder-tree-demo.gif new file mode 100644 index 00000000..46819d3b Binary files /dev/null and b/assets/folder-tree-demo.gif differ diff --git a/config/env.js b/config/env.js deleted file mode 100644 index ce99b6b4..00000000 --- a/config/env.js +++ /dev/null @@ -1,36 +0,0 @@ -// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be -// injected into the application via DefinePlugin in Webpack configuration. - -var REACT_APP = /^REACT_APP_/i; - -function getClientEnvironment(publicUrl) { - var raw = Object - .keys(process.env) - .filter(key => REACT_APP.test(key)) - .reduce((env, key) => { - env[key] = process.env[key]; - return env; - }, { - // Useful for determining whether we’re running in production mode. - // Most importantly, it switches React into the correct mode. - 'NODE_ENV': process.env.NODE_ENV || 'development', - // Useful for resolving the correct path to static assets in `public`. - // For example, . - // This should only be used as an escape hatch. Normally you would put - // images into the `src` and `import` them in code to get their paths. - 'PUBLIC_URL': publicUrl - }); - // Stringify all values so we can feed into Webpack DefinePlugin - var stringified = { - 'process.env': Object - .keys(raw) - .reduce((env, key) => { - env[key] = JSON.stringify(raw[key]); - return env; - }, {}) - }; - - return { raw, stringified }; -} - -module.exports = getClientEnvironment; diff --git a/config/jest/cssTransform.js b/config/jest/cssTransform.js deleted file mode 100644 index aa17d127..00000000 --- a/config/jest/cssTransform.js +++ /dev/null @@ -1,12 +0,0 @@ -// This is a custom Jest transformer turning style imports into empty objects. -// http://facebook.github.io/jest/docs/tutorial-webpack.html - -module.exports = { - process() { - return 'module.exports = {};'; - }, - getCacheKey(fileData, filename) { - // The output is always the same. - return 'cssTransform'; - }, -}; diff --git a/config/jest/fileTransform.js b/config/jest/fileTransform.js deleted file mode 100644 index 927eb305..00000000 --- a/config/jest/fileTransform.js +++ /dev/null @@ -1,10 +0,0 @@ -const path = require('path'); - -// This is a custom Jest transformer turning file imports into filenames. -// http://facebook.github.io/jest/docs/tutorial-webpack.html - -module.exports = { - process(src, filename) { - return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; - }, -}; diff --git a/config/paths.js b/config/paths.js deleted file mode 100644 index 97417b9c..00000000 --- a/config/paths.js +++ /dev/null @@ -1,79 +0,0 @@ -var path = require('path'); -var fs = require('fs'); -var url = require('url'); - -// Make sure any symlinks in the project folder are resolved: -// https://github.com/facebookincubator/create-react-app/issues/637 -var appDirectory = fs.realpathSync(process.cwd()); -function resolveApp(relativePath) { - return path.resolve(appDirectory, relativePath); -} - -// We support resolving modules according to `NODE_PATH`. -// This lets you use absolute paths in imports inside large monorepos: -// https://github.com/facebookincubator/create-react-app/issues/253. - -// It works similar to `NODE_PATH` in Node itself: -// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders - -// We will export `nodePaths` as an array of absolute paths. -// It will then be used by Webpack configs. -// Jest doesn’t need this because it already handles `NODE_PATH` out of the box. - -// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. -// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. -// https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 - -var nodePaths = (process.env.NODE_PATH || '') - .split(process.platform === 'win32' ? ';' : ':') - .filter(Boolean) - .filter(folder => !path.isAbsolute(folder)) - .map(resolveApp); - -var envPublicUrl = process.env.PUBLIC_URL; - -function ensureSlash(path, needsSlash) { - var hasSlash = path.endsWith('/'); - if (hasSlash && !needsSlash) { - return path.substr(path, path.length - 1); - } else if (!hasSlash && needsSlash) { - return path + '/'; - } else { - return path; - } -} - -function getPublicUrl(appPackageJson) { - return envPublicUrl || require(appPackageJson).homepage; -} - -// We use `PUBLIC_URL` environment variable or "homepage" field to infer -// "public path" at which the app is served. -// Webpack needs to know it to put the right