carbon-react
Version:
A library of reusable React components and an interface for easily building user interfaces based on Flux.
497 lines (411 loc) • 14.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Tab = exports.Tabs = undefined;
var _react2 = require('react');
var _react3 = _interopRequireDefault(_react2);
var _babelTransform = require('livereactload/babel-transform');
var _babelTransform2 = _interopRequireDefault(_babelTransform);
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; };
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; }; }();
var _class, _temp2;
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _immutable = require('immutable');
var _immutable2 = _interopRequireDefault(_immutable);
var _tab = require('./tab');
var _tab2 = _interopRequireDefault(_tab);
var _lodash = require('lodash');
var _classnames = require('classnames');
var _classnames2 = _interopRequireDefault(_classnames);
var _events = require('./../../utils/helpers/events');
var _events2 = _interopRequireDefault(_events);
var _tags = require('../../utils/helpers/tags');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
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; }
var _components = {
Tabs: {
displayName: 'Tabs'
}
};
var _livereactloadBabelTransform2 = (0, _babelTransform2.default)({
filename: 'src/components/tabs/tabs.js',
components: _components,
locals: [],
imports: [_react3.default]
});
function _wrapComponent(id) {
return function (Component) {
return _livereactloadBabelTransform2(Component, id);
};
}
/**
* A Tabs widget.
*
* == How to use a Tabs Widget in a component:
*
* In your file
*
* import { Tabs, Tab } from 'components/tabs';
*
* To render a Tabs Widget:
*
* <Tabs>
* <Tab title='Title 1' tabId='uniqueId1'>
*
* <Textbox />
* <Textbox />
*
* </Tab>
*
* <Tab title='Title 2' tabId='uniqueId2'>
*
* <Date />
* <Textbox />
*
* </Tab>
* </Tabs>
*
* Optionally, you can pass `renderHiddenTabs` prop to the Tabs. By default this is
* set to true and therefore all tabs will be rendered. The selected tab will have
* a class of `selected` and all other tabs will have a class of `hidden` which sets
* their display to `none`.
*
* Setting `renderHiddenTabs to false will add a small performance improvement as
* all previously hidden tabs will not be rendered to the page.
*
* If you are using the tab component within a form all tabs should be rendered so that
* form validation can work correctly.
*
* The tabs widget also allows you to select a tab on page load. By default this is set
* to the first tab. To set a different tab on page load pass a tabId to the
* selectedTabId prop as shown in the example below.
*
* To render a Tabs Widget with Options:
*
* <Tabs renderHiddenTabs={ false } selectedTabId='uniqueId2' >
* <Tab title='Title 1' tabId='uniqueId1'>
*
* <Textbox />
* <Textbox />
*
* </Tab>
*
* <Tab title='Title 2' tabId='uniqueId2'>
*
* <Date />
* <Textbox />
*
* </Tab>
* </Tabs>
*
* @class Tabs
* @constructor
*/
var Tabs = _wrapComponent('Tabs')((_temp2 = _class = function (_React$Component) {
_inherits(Tabs, _React$Component);
function Tabs() {
var _ref;
var _temp, _this, _ret;
_classCallCheck(this, Tabs);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = Tabs.__proto__ || Object.getPrototypeOf(Tabs)).call.apply(_ref, [this].concat(args))), _this), _this._window = window, _this.state = {
/**
* Tracks the validity of each tab
*
* @property tabValidity
* @type {Object}
*/
tabValidity: _immutable2.default.Map(),
/**
* Tracks the warning of each tab
*
* @property tabWarning
* @type {Object}
*/
tabWarning: _immutable2.default.Map()
/**
* A lifecycle method that is called after before initial render.
* Can set up state of component without causing a re-render
*
* @method componentWillMount
*/
}, _this.changeValidity = function (id, valid) {
_this.setState({ tabValidity: _this.state.tabValidity.set(id, valid) });
}, _this.changeWarning = function (id, warning) {
_this.setState({ tabWarning: _this.state.tabWarning.set(id, warning) });
}, _this.handleTabClick = function (ev) {
if (_events2.default.isEnterKey(ev) || !_events2.default.isEventType(ev, 'keydown')) {
var tabid = ev.target.dataset.tabid;
_this.updateVisibleTab(tabid);
}
}, _this.tabsHeaderClasses = function () {
return (0, _classnames2.default)('carbon-tabs__headers', 'carbon-tabs__headers--align-' + _this.props.align, 'carbon-tabs__headers');
}, _this.tabHeaderClasses = function (tab) {
var tabHasError = _this.state.tabValidity.get(tab.props.tabId) == false,
tabHasWarning = _this.state.tabWarning.get(tab.props.tabId) == true && !tabHasError;
return (0, _classnames2.default)('carbon-tabs__headers__header', tab.props.headerClassName, {
'carbon-tabs__headers__header--error': tabHasError,
'carbon-tabs__headers__header--warning': tabHasWarning,
'carbon-tabs__headers__header--selected': tab.props.tabId === _this.state.selectedTabId
});
}, _temp), _possibleConstructorReturn(_this, _ret);
}
/**
* Store the window object as property.
*
* @property _window
* @type {Object}
*/
_createClass(Tabs, [{
key: 'getChildContext',
/**
* Returns tabs object to tab component.
*
* @method getChildContext
*/
value: function getChildContext() {
return {
tabs: {
changeValidity: this.changeValidity,
changeWarning: this.changeWarning
}
};
}
}, {
key: 'componentWillMount',
value: function componentWillMount() {
var selectedTabId = void 0;
if (this.props.selectedTabId) {
selectedTabId = this.props.selectedTabId;
} else {
var hash = this._window.location.hash.substring(1);
if (Array.isArray(this.props.children)) {
var children = (0, _lodash.compact)(this.props.children),
useHash = false;
if (hash) {
for (var index in children) {
var child = children[index];
if (child.props.tabId == hash) {
useHash = true;
break;
}
}
}
selectedTabId = useHash ? hash : children[0].props.tabId;
} else {
selectedTabId = this.props.children.props.tabId;
}
}
this.setState({ selectedTabId: selectedTabId });
}
/**
* A lifecycle method that is called when props are updated.
* Used here to change the visible tab when selectedTabId is updated.
*
* @method componentWillReceiveProps
* @param {object} nextProps
*/
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
if (this.props.selectedTabId !== nextProps.selectedTabId && nextProps.selectedTabId !== this.state.selectedTabId) {
this.updateVisibleTab(nextProps.selectedTabId);
}
}
/**
* Sets the validity state of the given tab (id) to the
* given state (valid)
*
* @method changeValidity
* @param {Number} id tab identifier
* @param {Boolean} state of tab child
*/
/**
* Sets the warning state of the given tab (id)
*
* @method changeWarning
* @param {Number} id tab identifier
* @param {Boolean} state of tab child
*/
/**
* Handles the changing of tabs
*
* @method handleTabClick
* @param {Event} ev Click Event
*/
}, {
key: 'updateVisibleTab',
value: function updateVisibleTab(tabid) {
var url = '' + this._window.location.origin + this._window.location.pathname + '#' + tabid;
this._window.history.replaceState(null, 'change-tab', url);
this.setState({ selectedTabId: tabid });
if (this.props.onTabChange) {
this.props.onTabChange(tabid);
}
}
/**
* Classes to be applied to the whole tabs component
*
* @method mainClasses Main Class getter
*/
}, {
key: 'render',
/**
* Renders the component.
*
* @method render
*/
value: function render() {
return _react3.default.createElement(
'div',
_extends({ className: this.mainClasses }, (0, _tags.tagComponent)('tabs', this.props)),
this.tabHeaders,
this.tabs
);
}
}, {
key: 'mainClasses',
get: function get() {
return (0, _classnames2.default)('carbon-tabs', 'carbon-tabs__position-' + this.props.position, this.props.className);
}
}, {
key: 'tabHeaders',
/**
* Build the headers for the tab component
*
* @method tabHeaders
* @return Unordered list of tab titles
*/
get: function get() {
var _this2 = this;
var tabTitles = (0, _lodash.compact)(_react3.default.Children.toArray(this.props.children)).map(function (child) {
return _react3.default.createElement(
'li',
{
className: _this2.tabHeaderClasses(child),
'data-element': 'select-tab',
'data-tabid': child.props.tabId,
key: child.props.tabId,
onClick: _this2.handleTabClick,
onKeyDown: _this2.handleTabClick,
ref: child.props.tabId + '-tab',
tabIndex: '0'
},
child.props.title
);
});
return _react3.default.createElement(
'ul',
{ className: this.tabsHeaderClasses() },
tabTitles
);
}
/**
* Builds the single currently selected tab
*
* @method visibleTab
* @return {JSX} visible tab
*/
}, {
key: 'visibleTab',
get: function get() {
var _this3 = this;
var visibleTab = void 0;
(0, _lodash.compact)(_react3.default.Children.toArray(this.props.children)).map(function (child) {
if (child.props.tabId == _this3.state.selectedTabId) {
visibleTab = child;
}
});
return _react3.default.cloneElement(visibleTab, { className: 'carbon-tab--selected' });
}
/**
* Builds all tabs where non selected tabs have class of hidden
*
* @method tabs
* @return {JSX} all tabs
*/
}, {
key: 'tabs',
get: function get() {
var _this4 = this;
if (!this.props.renderHiddenTabs) {
return this.visibleTab;
}
var tabs = (0, _lodash.compact)(_react3.default.Children.toArray(this.props.children)).map(function (child) {
var klass = 'hidden';
if (child.props.tabId === _this4.state.selectedTabId) {
klass = 'carbon-tab--selected';
}
return _react3.default.cloneElement(child, { className: klass });
});
return tabs;
}
}]);
return Tabs;
}(_react3.default.Component), _class.propTypes = {
/**
* Should the unfocussed tabs be rendered to the page
*
* @property renderHiddenTabs
* @type {Boolean}
* @default true
*/
renderHiddenTabs: _propTypes2.default.bool,
/**
* The tab to be displayed updating this prop will change the visible tab.
* Defaults to the first tab upon initial load.
*
* @property selectedTabId
* @type {String}
* @default firstTab
*/
selectedTabId: _propTypes2.default.string,
/**
* Individual tabs
*
* @property children
* @type {Object | Array}
*/
children: _propTypes2.default.oneOfType([_propTypes2.default.array, _propTypes2.default.object]).isRequired,
/**
* Aligns the tab headers
*
* @property align
* @type {String}
*/
align: _propTypes2.default.string,
/**
* Emitted when the visible tab is changed
*
* @property onTabChange
* @type {Func}
*/
onTabChange: _propTypes2.default.func,
/**
* The position of tabs with respect to the content (top (default) or left)
*
* @property position
* @type {String}
*/
position: _propTypes2.default.string
}, _class.defaultProps = {
renderHiddenTabs: true,
align: 'left',
position: 'top'
}, _class.childContextTypes = {
/**
* Defines a context object for tab of the tabs component.
* https://facebook.github.io/react/docs/context.html
*
* @property tab
* @type {Object}
*/
tabs: _propTypes2.default.object }, _temp2));
exports.Tabs = Tabs;
exports.Tab = _tab2.default;