├── .babelrc ├── .gitignore ├── .npmignore ├── demos ├── index.js ├── opinionated.css ├── index.html ├── uncontrolled.js └── controlled.js ├── .eslintrc ├── server.js ├── bower.json ├── webpack.prod.config.js ├── webpack.config.js ├── react-treeview.css ├── package.json ├── HISTORY.md ├── src └── react-treeview.jsx └── README.md /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | bower_components 4 | node_modules 5 | lib 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /.eslintrc 2 | /.gitignore 3 | /.npmignore 4 | /bower.json 5 | /bower_components/ 6 | /build/ 7 | /demos/ 8 | /react-treeview.sublime-* 9 | /server.js 10 | /src/ 11 | /webpack.* 12 | -------------------------------------------------------------------------------- /demos/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Controlled from './controlled'; 4 | import Uncontrolled from './uncontrolled'; 5 | 6 | ReactDOM.render(, document.getElementById('controlled')); 7 | ReactDOM.render(, document.getElementById('uncontrolled')); 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "jsx": true, 4 | "modules": true 5 | }, 6 | "env": { 7 | "browser": true, 8 | "node": true 9 | }, 10 | "parser": "babel-eslint", 11 | "rules": { 12 | "comma-dangle": [2, "always-multiline"], 13 | "quotes": [2, "single"], 14 | "strict": [2, "never"], 15 | "react/jsx-uses-react": 2, 16 | "react/jsx-uses-vars": 2, 17 | "react/react-in-jsx-scope": 2 18 | }, 19 | "plugins": [ 20 | "react" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | publicPath: config.output.publicPath, 7 | hot: true, 8 | stats: { 9 | chunkModules: false, 10 | colors: true, 11 | }, 12 | }).listen(3000, 'localhost', function (err) { 13 | if (err) { 14 | console.log(err); 15 | } 16 | 17 | console.log('Listening at localhost:3000'); 18 | }); 19 | -------------------------------------------------------------------------------- /demos/opinionated.css: -------------------------------------------------------------------------------- 1 | .node { 2 | -moz-transition: all 0.5s; 3 | -o-transition: all 0.5s; 4 | -ms-transition: all 0.5s; 5 | -webkit-transition: all 0.5s; 6 | transition: all 0.5s; 7 | border-radius: 3px; 8 | } 9 | 10 | .node:hover { 11 | background-color: rgb(220, 245, 243); 12 | cursor: pointer; 13 | } 14 | 15 | .info, .node { 16 | padding: 2px 10px 2px 5px; 17 | font: 14px Helvetica, Arial, sans-serif; 18 | -webkit-user-select: none; 19 | -moz-user-select: none; 20 | -ms-user-select: none; 21 | user-select: none; 22 | } 23 | 24 | .tree-view_arrow { 25 | -moz-transition: all 0.1s; 26 | -o-transition: all 0.1s; 27 | -ms-transition: all 0.1s; 28 | -webkit-transition: all 0.1s; 29 | transition: all 0.1s; 30 | } 31 | 32 | .tree-view_arrow-empty { 33 | color: yellow; 34 | } 35 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-treeview", 3 | "version": "0.4.6", 4 | "author": "chenglou ", 5 | "description": "Easy, light, flexible tree view made with React.", 6 | "main": [ 7 | "build/react-treeview.js", 8 | "react-treeview.css" 9 | ], 10 | "ignore": [ 11 | "**/.*", 12 | "node_modules", 13 | "bower_components", 14 | "package.json", 15 | "demos", 16 | "src", 17 | "server.js", 18 | "webpack.config.js", 19 | "webpack.prod.config.js" 20 | ], 21 | "keywords": [ 22 | "facebook", 23 | "react", 24 | "treeview", 25 | "tree", 26 | "view", 27 | "treenode" 28 | ], 29 | "dependencies": { 30 | "react": ">=0.12.0" 31 | }, 32 | "devDependencies": {}, 33 | "license": "MIT", 34 | "resolutions": { 35 | "react": "~0.12.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | // currently, this is for bower 4 | var config = { 5 | devtool: 'sourcemap', 6 | entry: { 7 | index: './src/react-treeview.jsx', 8 | }, 9 | output: { 10 | path: path.join(__dirname, 'build'), 11 | publicPath: 'build/', 12 | filename: 'react-treeview.js', 13 | sourceMapFilename: 'react-treeview.map', 14 | library: 'TreeView', 15 | libraryTarget: 'umd', 16 | }, 17 | module: { 18 | loaders: [{ 19 | test: /\.(js|jsx)/, 20 | loader: 'babel', 21 | }], 22 | }, 23 | plugins: [], 24 | resolve: { 25 | extensions: ['', '.js', '.jsx'], 26 | }, 27 | externals: { 28 | 'react': { 29 | root: 'React', 30 | commonjs2: 'react', 31 | commonjs: 'react', 32 | amd: 'react', 33 | }, 34 | }, 35 | }; 36 | 37 | module.exports = config; 38 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | var entry = ['./demos/index.js']; 5 | 6 | if (process.env.NODE_ENV === 'development') { 7 | entry = entry.concat([ 8 | 'webpack-dev-server/client?http://localhost:3000', 9 | 'webpack/hot/only-dev-server', 10 | ]); 11 | } 12 | 13 | module.exports = { 14 | devtool: 'eval', 15 | entry: entry, 16 | output: { 17 | path: path.join(__dirname, 'demos'), 18 | filename: 'bundle.js', 19 | publicPath: '/demos/', 20 | }, 21 | plugins: [ 22 | new webpack.HotModuleReplacementPlugin(), 23 | new webpack.NoErrorsPlugin(), 24 | ], 25 | resolve: { 26 | extensions: ['', '.js', '.jsx'], 27 | }, 28 | module: { 29 | loaders: [{ 30 | test: /\.jsx?$/, 31 | loaders: ['react-hot', 'babel'], 32 | exclude: /build|lib|node_modules/, 33 | }], 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /react-treeview.css: -------------------------------------------------------------------------------- 1 | /* the tree node's style */ 2 | .tree-view { 3 | overflow-y: hidden; 4 | } 5 | 6 | .tree-view_item { 7 | /* immediate child of .tree-view, for styling convenience */ 8 | } 9 | 10 | /* style for the children nodes container */ 11 | .tree-view_children { 12 | margin-left: 16px; 13 | } 14 | 15 | .tree-view_children-collapsed { 16 | height: 0px; 17 | } 18 | 19 | .tree-view_arrow { 20 | cursor: pointer; 21 | margin-right: 6px; 22 | display: inline-block; 23 | -webkit-user-select: none; 24 | -moz-user-select: none; 25 | -ms-user-select: none; 26 | user-select: none; 27 | } 28 | 29 | .tree-view_arrow:after { 30 | content: '▾'; 31 | } 32 | 33 | /* rotate the triangle to close it */ 34 | .tree-view_arrow-collapsed { 35 | -webkit-transform: rotate(-90deg); 36 | -moz-transform: rotate(-90deg); 37 | -ms-transform: rotate(-90deg); 38 | transform: rotate(-90deg); 39 | } 40 | -------------------------------------------------------------------------------- /demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Some Trees 7 | 8 | 9 | 24 | 25 | 26 | 27 |
28 |
29 | Uncontrolled 30 |
31 |
32 |
33 | Controlled 34 |
35 |
36 |
37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-treeview", 3 | "version": "0.4.6", 4 | "description": "Easy, light, flexible tree view made with React.", 5 | "main": "lib/react-treeview.js", 6 | "directories": { 7 | "demos": "demos" 8 | }, 9 | "scripts": { 10 | "start": "NODE_ENV=development node server.js", 11 | "lint": "eslint src demos", 12 | "build-demos": "NODE_ENV=production webpack -p", 13 | "prerelease": "babel src --out-dir lib && NODE_ENV=production webpack --config webpack.prod.config.js" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/chenglou/react-treeview.git" 18 | }, 19 | "keywords": [ 20 | "facebook", 21 | "react", 22 | "treeview", 23 | "tree-view", 24 | "tree", 25 | "view", 26 | "treenode", 27 | "tree-node", 28 | "ui", 29 | "react-component" 30 | ], 31 | "author": "chenglou ", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/chenglou/react-treeview/issues" 35 | }, 36 | "homepage": "https://github.com/chenglou/react-treeview", 37 | "peerDependencies": { 38 | "react": ">=0.14.0" 39 | }, 40 | "devDependencies": { 41 | "babel": "^5.8.23", 42 | "babel-eslint": "^4.1.3", 43 | "babel-loader": "^5.3.1", 44 | "eslint-plugin-react": "^3.5.1", 45 | "prop-types": "^15.5.10", 46 | "react": ">=15.0.0", 47 | "react-dom": ">=15.0.0", 48 | "react-hot-loader": "^1.3.0", 49 | "webpack": "^1.10.1", 50 | "webpack-dev-server": "^1.10.1" 51 | }, 52 | "dependencies": { 53 | "prop-types": "^15.5.8" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | ## 0.4.5 (April 18th 2016) 2 | - Fix build output not showing. Sorry! 3 | 4 | ## 0.4.4 (April 18th 2016) 5 | - Bump React version requirement. 6 | 7 | ## 0.4.3 (February 7th 2016) 8 | - Support for React 0.14 official. 9 | - New prop `itemClassName` to assign class name on the `TreeView` node itself. #28 10 | - Arrow symbol is now styled via CSS instead of hard-coded inside the DOM. This means you can now use your own styling for the arrow! #27 11 | 12 | ## 0.4.2 (September 12th 2015) 13 | - Support for React 0.14, beta and rc. 14 | 15 | ## 0.4.0 (July 31th 2015) 16 | - Repo revamp. No breaking change beside the change in directory structure. New location for npm: `lib/`. Location for bower & others: `build/`. The CSS is on root level. 17 | - Expose `tree-view_item` css class, the immediate child of `.tree-view`, for styling convenience. 18 | 19 | ## 0.3.12 (May 7th 2015) 20 | - Upgrade React dependency to accept >=0.12.0. 21 | 22 | ## 0.3.11 (December 2nd 2014) 23 | - Upgrade React to 0.12.1. 24 | - Fix `propTypes` warning. 25 | 26 | ## 0.3.10 (November 9th 2014) 27 | - Perf improvement. 28 | 29 | ## 0.3.9 (November 8th 2014) 30 | - Bump React to 0.12. 31 | 32 | ## 0.3.8 (September 29th 2014) 33 | - Make AMD with Webpack work. 34 | - Bump React version to 0.11.2. 35 | 36 | ## 0.3.7 (September 17th 2014) 37 | - Support for AMD. 38 | 39 | ## 0.3.3-0.3.5 (July 8th 2014) 40 | - Fix case-sensitive `require` for Linux. 41 | 42 | ## 0.3.2 (May 12th 2014) 43 | - Fix bug where `onClick` doesn't trigger. 44 | 45 | ## 0.3.1 (May 12th 2014) 46 | - New API. Breaking. It's a superset of the previous API, so everything should be reproducible.The new only Only exposes a `TreeView` and let natural recursion construct the tree. 47 | - Bump React version. 48 | 49 | ### 0.2.1 (September 21st 2013) 50 | - Stable API. 51 | 52 | ## 0.0.0 (July 13th 2013) 53 | - Initial release. 54 | -------------------------------------------------------------------------------- /demos/uncontrolled.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TreeView from '../src/react-treeview'; 3 | 4 | // This example data format is totally arbitrary. No data massaging is 5 | // required and you use regular js in `render` to iterate through and 6 | // construct your nodes. 7 | const dataSource = [ 8 | { 9 | type: 'Employees', 10 | collapsed: false, 11 | people: [ 12 | {name: 'Paul Gordon', age: 25, sex: 'male', role: 'coder', collapsed: false}, 13 | {name: 'Sarah Lee', age: 23, sex: 'female', role: 'jqueryer', collapsed: false}, 14 | ], 15 | }, 16 | { 17 | type: 'CEO', 18 | collapsed: false, 19 | people: [ 20 | {name: 'Drew Anderson', age: 35, sex: 'male', role: 'boss', collapsed: false}, 21 | ], 22 | }, 23 | ]; 24 | 25 | // For the sake of simplicity, we're gonna use `defaultCollapsed`. Usually, a 26 | // [controlled component](http://facebook.github.io/react/docs/forms.html#controlled-components) 27 | // is preferred. 28 | class CompanyPeople extends React.Component { 29 | render() { 30 | return ( 31 |
32 | {dataSource.map((node, i) => { 33 | const type = node.type; 34 | const label = {type}; 35 | return ( 36 | 37 | {node.people.map(person => { 38 | const label2 = {person.name}; 39 | return ( 40 | 41 |
age: {person.age}
42 |
sex: {person.sex}
43 |
role: {person.role}
44 |
45 | ); 46 | })} 47 |
48 | ); 49 | })} 50 |
51 | ); 52 | } 53 | } 54 | 55 | export default CompanyPeople; 56 | -------------------------------------------------------------------------------- /src/react-treeview.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class TreeView extends React.Component { 5 | propTypes: { 6 | collapsed: PropTypes.bool, 7 | defaultCollapsed: PropTypes.bool, 8 | nodeLabel: PropTypes.node.isRequired, 9 | className: PropTypes.string, 10 | itemClassName: PropTypes.string, 11 | childrenClassName: PropTypes.string, 12 | treeViewClassName: PropTypes.string, 13 | } 14 | 15 | constructor(props) { 16 | super(props); 17 | 18 | this.state = { 19 | collapsed: props.defaultCollapsed 20 | }; 21 | this.handleClick = this.handleClick.bind(this); 22 | } 23 | 24 | handleClick(...args) { 25 | this.setState({ collapsed: !this.state.collapsed }); 26 | if (this.props.onClick) { 27 | this.props.onClick(...args); 28 | } 29 | } 30 | 31 | render() { 32 | const { 33 | collapsed = this.state.collapsed, 34 | className = '', 35 | itemClassName = '', 36 | treeViewClassName = '', 37 | childrenClassName = '', 38 | nodeLabel, 39 | children, 40 | defaultCollapsed, 41 | ...rest 42 | } = this.props; 43 | 44 | let arrowClassName = 'tree-view_arrow'; 45 | let containerClassName = 'tree-view_children'; 46 | if (collapsed) { 47 | arrowClassName += ' tree-view_arrow-collapsed'; 48 | containerClassName += ' tree-view_children-collapsed'; 49 | } 50 | 51 | const arrow = ( 52 |
57 | ); 58 | 59 | return ( 60 |
61 |
62 | {arrow} 63 | {nodeLabel} 64 |
65 |
66 | {collapsed ? null : children} 67 |
68 |
69 | ); 70 | } 71 | } 72 | 73 | export default TreeView; 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React-treeview [![npm version](https://badge.fury.io/js/react-treeview.svg)](https://www.npmjs.com/package/react-treeview) [![Bower version](https://badge.fury.io/bo/react-treeview.svg)](http://badge.fury.io/bo/react-treeview) 2 | 3 | Easy, light, flexible treeview made with [React](http://facebook.github.io/react/). 4 | 5 | [Demos](https://cdn.rawgit.com/chenglou/react-treeview/aa72ed8b9e0b31fabc09e2f8bd4084947d48bb09/demos/index.html) from the [demos folder](https://github.com/chenglou/react-treeview/tree/aa72ed8b9e0b31fabc09e2f8bd4084947d48bb09/demos). 6 | 7 | ## install 8 | 9 | Npm: 10 | ```sh 11 | npm install react-treeview 12 | ``` 13 | 14 | Bower: 15 | ```sh 16 | bower install react-treeview 17 | ``` 18 | 19 | The CSS file: 20 | 21 | ```html 22 | 23 | ``` 24 | 25 | ## API 26 | 27 | #### <TreeView /> 28 | The component accepts [these props](https://github.com/chenglou/react-treeview/blob/aa72ed8b9e0b31fabc09e2f8bd4084947d48bb09/src/react-treeview.jsx#L5-L9). 29 | 30 | - `collapsed`: whether the node is collapsed or not. 31 | - `defaultCollapsed`: the [uncontrolled](http://facebook.github.io/react/docs/forms.html#uncontrolled-components) equivalent to `collapsed`. 32 | - `nodeLabel`: the component or string (or anything renderable) that's displayed beside the TreeView arrow. 33 | - `itemClassName`: the class name of the `.tree-view_item` div. 34 | - `treeViewClassName`: the class name of the `.tree-view` div. 35 | - `childrenClassName`: the class name of the `.tree-view_children` item div. 36 | 37 | TreeViews can be naturally nested. 38 | 39 | The extra properties transferred onto the arrow, so all attributes and events naturally work on it. 40 | 41 | ## Styling 42 | The CSS is flexible, commented and made to be easily customized. Feel free to inspect the demo's classes and check the [short CSS code](https://github.com/chenglou/react-treeview/blob/aa72ed8b9e0b31fabc09e2f8bd4084947d48bb09/react-treeview.css). 43 | 44 | ## Build It Yourself/Run the Demos 45 | 46 | Build: `npm install && npm run prerelease` 47 | 48 | Demos: `npm install && npm start && open http://localhost:3000` 49 | 50 | ## License 51 | 52 | MIT. 53 | -------------------------------------------------------------------------------- /demos/controlled.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TreeView from '../src/react-treeview'; 3 | 4 | // This example data format is totally arbitrary. No data massaging is 5 | // required and you use regular js in `render` to iterate through and 6 | // construct your nodes. 7 | const dataSource = [ 8 | ['Apple', 'Orange'], 9 | ['Facebook', 'Google'], 10 | ['Celery', 'Cheeseburger'], 11 | ]; 12 | 13 | // A controlled TreeView, akin to React's controlled inputs 14 | // (http://facebook.github.io/react/docs/forms.html#controlled-components), has 15 | // many benefits. Among others, you can expand/collapse everything (i.e. easily 16 | // trigger those somewhere else). 17 | class Lists extends React.Component { 18 | 19 | constructor(props) { 20 | super(props); 21 | 22 | this.state = { 23 | collapsedBookkeeping: dataSource.map(() => false) 24 | }; 25 | this.handleClick = this.handleClick.bind(this); 26 | this.collapseAll = this.collapseAll.bind(this); 27 | } 28 | 29 | handleClick(i) { 30 | let [...collapsedBookkeeping] = this.state.collapsedBookkeeping; 31 | collapsedBookkeeping[i] = !collapsedBookkeeping[i]; 32 | this.setState({collapsedBookkeeping: collapsedBookkeeping}); 33 | } 34 | 35 | collapseAll() { 36 | this.setState({ 37 | collapsedBookkeeping: this.state.collapsedBookkeeping.map(() => true), 38 | }); 39 | } 40 | 41 | render() { 42 | const collapsedBookkeeping = this.state.collapsedBookkeeping; 43 | return ( 44 |
45 | 46 | {dataSource.map((node, i) => { 47 | // Let's make it so that the tree also toggles when we click the 48 | // label. Controlled components make this effortless. 49 | const label = 50 | 51 | Type {i} 52 | ; 53 | return ( 54 | 59 | {node.map(entry =>
{entry}
)} 60 |
61 | ); 62 | })} 63 |
64 | ); 65 | } 66 | } 67 | 68 | export default Lists; 69 | --------------------------------------------------------------------------------