├── .eslintrc ├── .gitignore ├── README.md ├── example ├── basic.jsx ├── example.config.js ├── index.html ├── main.js └── server.config.js ├── lib ├── css │ └── default.css ├── index.js ├── tab.js └── tabs.js ├── mocha.opts ├── package.json ├── src ├── css │ └── default.css ├── index.jsx ├── tab.jsx └── tabs.jsx └── tests ├── setup.js └── test.jsx /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "es6": true 5 | }, 6 | "ecmaFeatures": { 7 | "blockBindings": true, 8 | "forOf": true, 9 | "jsx": true, 10 | "modules": true 11 | }, 12 | "rules": { 13 | "indent": [2, 2], 14 | "max-len": 0, 15 | "semi": 0, 16 | "quotes": 0, 17 | "no-console": 0, 18 | "no-trailing-spaces": 0, 19 | "curly": 0, 20 | "camelcase": 0, 21 | "react/jsx-boolean-value": 1, 22 | "react/jsx-quotes": 1, 23 | "react/jsx-no-undef": 1, 24 | "react/jsx-uses-react": 1, 25 | "react/jsx-uses-vars": 1, 26 | "react/no-did-mount-set-state": 1, 27 | "react/no-did-update-set-state": 1, 28 | "react/no-multi-comp": 1, 29 | "react/no-unknown-property": 1, 30 | "react/prop-types": 1, 31 | "react/react-in-jsx-scope": 1, 32 | "react/self-closing-comp": 1, 33 | "react/sort-comp": 1, 34 | "react/wrap-multilines": 1, 35 | "new-cap": [1, {newIsCap: true, capIsNew: false}], 36 | }, 37 | "plugins": [ 38 | "react" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##Tabs 2 | 3 | A basic tabs component, with a few customizable props. 4 | 5 | ##Install 6 | 7 | `npm install legit-tabs` 8 | 9 | ##Example 10 | 11 | ~~~js 12 | import {Tabs, Tab} from 'legit-tabs'; 13 | 14 | 15 | 16 | hey 17 | 18 | 19 | whats up 20 | 21 | 22 | hello 23 | 24 | 25 | ~~~ 26 | 27 | ##Props 28 | 29 | **Tabs**: 30 | 31 | - `active`: Name of the tab you want to be active, defaults to the first one if empty 32 | - anything else you'd like to set on the containing ul 33 | 34 | **Tab**: 35 | 36 | - `name`: a unique name for the tab, represents the tab title. 37 | - `liClass`, `liStyle`: class on the li for the tab. 38 | - `contentClass`: class on the div that holds the tab content. 39 | - anything else you'd like to set on the tab's containing div. 40 | -------------------------------------------------------------------------------- /example/basic.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Tabs, Tab} from '../src/index'; 3 | 4 | require('../src/css/default.css'); 5 | 6 | export default class Basic extends React.Component { 7 | 8 | render() { 9 | return ( 10 | 11 | 12 | hey 13 | 14 | 15 | whats up 16 | 17 | 18 | hello 19 | 20 | 21 | ); 22 | } 23 | } 24 | 25 | React.render(, document.getElementById('react')); 26 | -------------------------------------------------------------------------------- /example/example.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | 4 | module.exports = { 5 | entry: './example/basic.jsx', 6 | output: { 7 | path: __dirname, 8 | filename: "[name].js" 9 | }, 10 | resolve: { 11 | extensions: ['', '.js', '.jsx', '.es6'], 12 | modulesDirectories: ['node_modules'] 13 | }, 14 | module: { 15 | loaders: [ 16 | { test: /\.jsx$|\.es6$|\.js$/, loaders: ['babel-loader?stage=0'], exclude: /node_modules/ }, 17 | { test: /\.css$/, loader: 'style-loader!css-loader' } 18 | ] 19 | }, 20 | plugins: [ 21 | new webpack.NoErrorsPlugin() 22 | ], 23 | devtool: "eval-source-map" 24 | }; 25 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Basic Example 4 | 18 | 19 | 20 |
21 |
22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /example/server.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | 4 | module.exports = { 5 | entry: { 6 | 'basic': [ 7 | 'webpack-dev-server/client?http://localhost:8881/', 8 | 'webpack/hot/only-dev-server', 9 | './example/basic.jsx' 10 | ] 11 | }, 12 | output: { 13 | path: __dirname, 14 | filename: "[name].js", 15 | publicPath: 'http://localhost:8881/', 16 | chunkFilename: '[id].chunk.js', 17 | sourceMapFilename: '[name].map' 18 | }, 19 | resolve: { 20 | extensions: ['', '.js', '.jsx', '.es6', '.css'], 21 | modulesDirectories: ['node_modules'] 22 | }, 23 | module: { 24 | loaders: [ 25 | { test: /\.jsx$|\.es6$|\.js$/, loaders: ['react-hot', 'babel-loader?stage=0'], exclude: /node_modules/ }, 26 | { test: /\.css$/, loader: 'style-loader!style!css' }, 27 | { test: /\.(jpe?g|png|gif)$/i, loader: 'url?limit=10000!img?progressive=true' } 28 | ] 29 | }, 30 | plugins: [ 31 | new webpack.NoErrorsPlugin() 32 | ], 33 | devtool: "eval-source-map" 34 | }; 35 | -------------------------------------------------------------------------------- /lib/css/default.css: -------------------------------------------------------------------------------- 1 | .accordion-tabs-minimal { 2 | line-height: 1.5; 3 | padding: 0; 4 | } 5 | 6 | .accordion-tabs-minimal::after { 7 | clear: both; 8 | content: ""; 9 | } 10 | 11 | .accordion-tabs-minimal li.tab-header-and-content { 12 | list-style: none; 13 | display: inline; 14 | } 15 | 16 | .accordion-tabs-minimal a.tab-link { 17 | background-color: white; 18 | border-top: 0; 19 | color: #333; 20 | display: inline-block; 21 | padding: 0.75em 1em; 22 | text-decoration: none; 23 | } 24 | 25 | .accordion-tabs-minimal a.tab-link:hover { 26 | color: #477DCA; 27 | cursor: pointer; 28 | } 29 | 30 | .accordion-tabs-minimal a.tab-link:focus { 31 | outline: none; 32 | } 33 | 34 | .accordion-tabs-minimal a.tab-link.is-active { 35 | border: solid 1px gainsboro; 36 | border-bottom-color: transparent; 37 | margin-bottom: -1px; 38 | } 39 | 40 | .accordion-tabs-minimal .tab-content { 41 | padding: 1.5em 1em; 42 | width: 100%; 43 | border-top: solid 1px gainsboro; 44 | float: left; 45 | } 46 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 8 | 9 | var _tabs = require('./tabs'); 10 | 11 | var _tabs2 = _interopRequireDefault(_tabs); 12 | 13 | var _tab = require('./tab'); 14 | 15 | var _tab2 = _interopRequireDefault(_tab); 16 | 17 | exports.Tabs = _tabs2['default']; 18 | exports.Tab = _tab2['default']; -------------------------------------------------------------------------------- /lib/tab.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 10 | 11 | var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 14 | 15 | function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } 16 | 17 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 18 | 19 | function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 20 | 21 | var _react = require('react'); 22 | 23 | var _react2 = _interopRequireDefault(_react); 24 | 25 | var Tab = (function (_React$Component) { 26 | _inherits(Tab, _React$Component); 27 | 28 | _createClass(Tab, null, [{ 29 | key: 'propTypes', 30 | value: { 31 | name: _react2['default'].PropTypes.string.isRequired, 32 | clicked: _react2['default'].PropTypes.func, 33 | active: _react2['default'].PropTypes.bool, 34 | children: _react2['default'].PropTypes.any.isRequired, 35 | liStyle: _react2['default'].PropTypes.object, 36 | liClass: _react2['default'].PropTypes.string 37 | }, 38 | enumerable: true 39 | }]); 40 | 41 | function Tab(props) { 42 | var _this = this; 43 | 44 | _classCallCheck(this, Tab); 45 | 46 | _get(Object.getPrototypeOf(Tab.prototype), 'constructor', this).call(this, props); 47 | 48 | this.clicked = function () { 49 | _this.props.clicked(_this.props.name); 50 | }; 51 | 52 | this.state = {}; 53 | } 54 | 55 | _createClass(Tab, [{ 56 | key: 'render', 57 | value: function render() { 58 | var _props = this.props; 59 | var liClass = _props.liClass; 60 | var liStyle = _props.liStyle; 61 | var contentClass = _props.contentClass; 62 | var active = _props.active; 63 | var name = _props.name; 64 | var children = _props.children; 65 | 66 | var rest = _objectWithoutProperties(_props, ['liClass', 'liStyle', 'contentClass', 'active', 'name', 'children']); 67 | 68 | delete rest.clicked; 69 | var linkClass = active ? 'is-active' : null; 70 | 71 | return _react2['default'].createElement( 72 | 'li', 73 | { className: 'tab-header-and-content ' + liClass, style: liStyle || null }, 74 | _react2['default'].createElement( 75 | 'a', 76 | { 77 | className: 'tab-link ' + linkClass, 78 | onClick: this.clicked }, 79 | name 80 | ), 81 | active ? _react2['default'].createElement( 82 | 'div', 83 | _extends({}, rest, { className: 'tab-content ' + contentClass }), 84 | children 85 | ) : null 86 | ); 87 | } 88 | }]); 89 | 90 | return Tab; 91 | })(_react2['default'].Component); 92 | 93 | exports['default'] = Tab; 94 | module.exports = exports['default']; -------------------------------------------------------------------------------- /lib/tabs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 10 | 11 | var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 14 | 15 | function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } 16 | 17 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 18 | 19 | function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 20 | 21 | var _react = require('react'); 22 | 23 | var _react2 = _interopRequireDefault(_react); 24 | 25 | var Tabs = (function (_React$Component) { 26 | _inherits(Tabs, _React$Component); 27 | 28 | _createClass(Tabs, null, [{ 29 | key: 'propTypes', 30 | value: { 31 | children: _react2['default'].PropTypes.array 32 | }, 33 | enumerable: true 34 | }]); 35 | 36 | function Tabs(props) { 37 | var _this = this; 38 | 39 | _classCallCheck(this, Tabs); 40 | 41 | _get(Object.getPrototypeOf(Tabs.prototype), 'constructor', this).call(this, props); 42 | 43 | this.clicked = function (active) { 44 | _this.setState({ active: active }); 45 | }; 46 | 47 | this.state = { 48 | active: props.active || props.children[0].props.name 49 | }; 50 | } 51 | 52 | _createClass(Tabs, [{ 53 | key: 'renderChildren', 54 | value: function renderChildren() { 55 | var _this2 = this; 56 | 57 | return _react2['default'].Children.map(this.props.children, function (child) { 58 | return _react2['default'].cloneElement(child, _extends({}, child.props, { 59 | clicked: _this2.clicked, 60 | active: _this2.state.active === child.props.name ? true : false 61 | })); 62 | }); 63 | } 64 | }, { 65 | key: 'render', 66 | value: function render() { 67 | var _props = this.props; 68 | var className = _props.className; 69 | 70 | var rest = _objectWithoutProperties(_props, ['className']); 71 | 72 | // This is a hack to make React 15 happy. We can't pass the `active` prop 73 | // through to the `ul` component. This component needs a bit of a rewrite 74 | // so we'll go ahead and just do this for now. 75 | delete rest.active; 76 | 77 | return _react2['default'].createElement( 78 | 'ul', 79 | _extends({ className: 'accordion-tabs-minimal ' + className }, rest), 80 | this.renderChildren() 81 | ); 82 | } 83 | }]); 84 | 85 | return Tabs; 86 | })(_react2['default'].Component); 87 | 88 | exports['default'] = Tabs; 89 | module.exports = exports['default']; -------------------------------------------------------------------------------- /mocha.opts: -------------------------------------------------------------------------------- 1 | --require ./tests/setup 2 | --full-trace 3 | --compilers js:babel/register 4 | --recursive ./tests/**/*.jsx 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "legit-tabs", 3 | "version": "0.2.4", 4 | "description": "tabs, done right", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "compile": "babel src --stage 0 --out-dir lib;", 8 | "prepublish": "babel src --stage 0 --out-dir lib;", 9 | "dev-server": "webpack-dev-server --config ./example/server.config.js --hot --port 8881", 10 | "example": "webpack --config ./example/example.config.js", 11 | "test": "mocha --opts ./mocha.opts; eslint ./src/ --ext .jsx,.js --global require,exports:true" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+ssh://git@github.com/legitcode/tabs.git" 16 | }, 17 | "keywords": [ 18 | "react-tabs", 19 | "react-component", 20 | "react", 21 | "table", 22 | "react-table", 23 | "bootstrap" 24 | ], 25 | "author": "Zach Silveira", 26 | "license": "ISC", 27 | "bugs": { 28 | "url": "https://github.com/legitcode/tabs/issues" 29 | }, 30 | "homepage": "https://github.com/legitcode/tabs#readme", 31 | "devDependencies": { 32 | "babel": "^5.8.23", 33 | "babel-core": "^5.8.23", 34 | "babel-eslint": "^4.1.1", 35 | "babel-loader": "^5.3.2", 36 | "css-loader": "^0.18.0", 37 | "eslint": "^0.24.1", 38 | "eslint-plugin-react": "^2.7.0", 39 | "expect": "^1.8.0", 40 | "mocha": "^2.2.5", 41 | "mocha-babel": "^3.0.0", 42 | "node-jsdom": "^3.1.5", 43 | "react-hot-loader": "^1.3.0", 44 | "style-loader": "^0.12.4", 45 | "webpack": "^1.12.1", 46 | "webpack-dev-server": "^1.10.1" 47 | }, 48 | "peerDependencies": { 49 | "react": ">=0.13.3" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/css/default.css: -------------------------------------------------------------------------------- 1 | .accordion-tabs-minimal { 2 | line-height: 1.5; 3 | padding: 0; 4 | } 5 | 6 | .accordion-tabs-minimal::after { 7 | clear: both; 8 | content: ""; 9 | } 10 | 11 | .accordion-tabs-minimal li.tab-header-and-content { 12 | list-style: none; 13 | display: inline; 14 | } 15 | 16 | .accordion-tabs-minimal a.tab-link { 17 | background-color: white; 18 | border-top: 0; 19 | color: #333; 20 | display: inline-block; 21 | padding: 0.75em 1em; 22 | text-decoration: none; 23 | } 24 | 25 | .accordion-tabs-minimal a.tab-link:hover { 26 | color: #477DCA; 27 | cursor: pointer; 28 | } 29 | 30 | .accordion-tabs-minimal a.tab-link:focus { 31 | outline: none; 32 | } 33 | 34 | .accordion-tabs-minimal a.tab-link.is-active { 35 | border: solid 1px gainsboro; 36 | border-bottom-color: transparent; 37 | margin-bottom: -1px; 38 | } 39 | 40 | .accordion-tabs-minimal .tab-content { 41 | padding: 1.5em 1em; 42 | width: 100%; 43 | border-top: solid 1px gainsboro; 44 | float: left; 45 | } 46 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import Tabs from './tabs' 2 | import Tab from './tab' 3 | 4 | export { 5 | Tabs, 6 | Tab 7 | } 8 | -------------------------------------------------------------------------------- /src/tab.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class Tab extends React.Component { 4 | static propTypes = { 5 | name: React.PropTypes.string.isRequired, 6 | clicked: React.PropTypes.func, 7 | active: React.PropTypes.bool, 8 | children: React.PropTypes.any.isRequired, 9 | liStyle: React.PropTypes.object, 10 | liClass: React.PropTypes.string 11 | } 12 | 13 | constructor(props) { 14 | super(props) 15 | this.state = {} 16 | } 17 | 18 | clicked = () => { 19 | this.props.clicked(this.props.name); 20 | } 21 | 22 | render() { 23 | const { 24 | liClass, 25 | liStyle, 26 | contentClass, 27 | active, 28 | name, 29 | children, 30 | ...rest 31 | } = this.props; 32 | delete rest.clicked; 33 | const linkClass = active ? 'is-active' : null; 34 | 35 | return ( 36 |
  • 37 | 40 | {name} 41 | 42 | 43 | {active ? 44 |
    45 | { children } 46 |
    47 | : null} 48 |
  • 49 | ) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/tabs.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class Tabs extends React.Component { 4 | static propTypes = { 5 | children: React.PropTypes.array 6 | } 7 | 8 | constructor(props) { 9 | super(props); 10 | 11 | this.state = { 12 | active: props.active || props.children[0].props.name 13 | }; 14 | } 15 | 16 | renderChildren(){ 17 | return React.Children.map(this.props.children, (child) => { 18 | return React.cloneElement(child, { 19 | ...child.props, 20 | clicked: this.clicked, 21 | active: this.state.active === child.props.name ? true : false 22 | }); 23 | }); 24 | } 25 | 26 | clicked = (active) => { 27 | this.setState({active}); 28 | } 29 | 30 | render() { 31 | const { className, ...rest } = this.props; 32 | 33 | // This is a hack to make React 15 happy. We can't pass the `active` prop 34 | // through to the `ul` component. This component needs a bit of a rewrite 35 | // so we'll go ahead and just do this for now. 36 | delete rest.active; 37 | 38 | return ( 39 | 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/setup.js: -------------------------------------------------------------------------------- 1 | /* globals global */ 2 | 3 | require("babel/register")({ 4 | stage: 0 5 | }); 6 | 7 | function propagateToGlobal (window) { 8 | for (let key in window) { 9 | if (!window.hasOwnProperty(key)) continue 10 | if (key in global) continue 11 | 12 | global[key] = window[key] 13 | } 14 | } 15 | 16 | var jsdom = require('jsdom'); 17 | 18 | var doc = jsdom.jsdom(''); 19 | var win = doc.defaultView; 20 | 21 | global.document = doc; 22 | global.window = win; 23 | 24 | propagateToGlobal(win); 25 | -------------------------------------------------------------------------------- /tests/test.jsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Legitcode/tabs/36a1938264df966aa4e2e92510a37f68fd7e3023/tests/test.jsx --------------------------------------------------------------------------------