aboutsummaryrefslogtreecommitdiffstats
path: root/src/react
diff options
context:
space:
mode:
Diffstat (limited to 'src/react')
-rw-r--r--src/react/Accordion.js40
-rw-r--r--src/react/Button.js37
-rw-r--r--src/react/Checkbox.js45
-rw-r--r--src/react/Checklist.js43
-rw-r--r--src/react/Input.js88
-rw-r--r--src/react/Modal.js55
-rw-r--r--src/react/ModalBody.js19
-rw-r--r--src/react/ModalFooter.js36
-rw-r--r--src/react/ModalHeader.js41
-rw-r--r--src/react/ModalTitle.js19
-rw-r--r--src/react/Panel.js18
-rw-r--r--src/react/PopupMenu.js39
-rw-r--r--src/react/PopupMenuItem.js34
-rw-r--r--src/react/Portal.js52
-rw-r--r--src/react/Radio.js58
-rw-r--r--src/react/RadioGroup.js40
-rw-r--r--src/react/SVGIcon.js47
-rw-r--r--src/react/Tab.js20
-rw-r--r--src/react/TabPane.js12
-rw-r--r--src/react/Tabs.js29
-rw-r--r--src/react/Tile.js33
-rw-r--r--src/react/TileFooter.js10
-rw-r--r--src/react/TileFooterCell.js7
-rw-r--r--src/react/TileInfo.js10
-rw-r--r--src/react/TileInfoLine.js7
-rw-r--r--src/react/index.js74
26 files changed, 913 insertions, 0 deletions
diff --git a/src/react/Accordion.js b/src/react/Accordion.js
new file mode 100644
index 0000000..3acdd24
--- /dev/null
+++ b/src/react/Accordion.js
@@ -0,0 +1,40 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import SVGIcon from './SVGIcon.js';
+
+class Accordion extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ open: props.defaultExpanded
+ };
+ }
+ render() {
+ const { children, title, className, dataTestId } = this.props;
+ const { open } = this.state;
+ return (
+ <div className={`sdc-accordion ${className}`}>
+ <div data-test-id={dataTestId} onClick={() => this.setState({ open: !open })} className='sdc-accordion-header'>
+ <SVGIcon name='chevronDown' iconClassName={open ? 'down' : ''} />
+ <div className='title'>{title}</div>
+ </div>
+ <div className={`sdc-accordion-body ${open ? 'open' : ''}`}>{children}</div>
+ </div>
+ );
+ }
+}
+
+Accordion.propTypes = {
+ title: PropTypes.string,
+ children: PropTypes.node,
+ expandByDefault: PropTypes.bool,
+ dataTestId: PropTypes.string
+};
+
+Accordion.defaultProps = {
+ title: '',
+ className: '',
+ defaultExpanded: false
+};
+
+export default Accordion;
diff --git a/src/react/Button.js b/src/react/Button.js
new file mode 100644
index 0000000..c628455
--- /dev/null
+++ b/src/react/Button.js
@@ -0,0 +1,37 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import SVGIcon from './SVGIcon.js';
+
+const Button = ({btnType, size, className, iconName, onClick, disabled, children, ...other}) => (
+ <button
+ onClick={onClick}
+ className={`sdc-button sdc-button__${btnType} ${size && `btn-${size}`} ${className} ${iconName}`}
+ disabled={disabled}
+ {...other}>
+ {
+ iconName ?
+ <SVGIcon name={iconName} label={children} labelPosition='right' />
+ :
+ children
+ }
+ </button>
+);
+
+Button.propTypes = {
+ btnType: PropTypes.string,
+ size: PropTypes.oneOf(['', 'default', 'x-small', 'small', 'medium', 'large']),
+ className: PropTypes.string,
+ iconName: PropTypes.string,
+ onClick: PropTypes.func,
+ disabled: PropTypes.bool
+};
+
+Button.defaultProps = {
+ btnType: 'primary',
+ size: '',
+ className: '',
+ iconName: '',
+ disabled: false
+};
+
+export default Button;
diff --git a/src/react/Checkbox.js b/src/react/Checkbox.js
new file mode 100644
index 0000000..bef6945
--- /dev/null
+++ b/src/react/Checkbox.js
@@ -0,0 +1,45 @@
+import React from 'react';
+
+class Checkbox extends React.Component {
+
+ render() {
+ let {checked = false, disabled, value, label, inputRef, className, name} = this.props;
+ let dataTestId = this.props['data-test-id'];
+
+ return (
+ <div className={`sdc-checkbox ${className || ''}`}>
+ <label>
+ <input
+ className='sdc-checkbox__input'
+ ref={inputRef}
+ data-test-id={dataTestId}
+ type='checkbox'
+ checked={checked}
+ name={name}
+ value={value}
+ onChange={(e) => this.onChange(e)}
+ disabled={disabled} />
+ <span className='sdc-checkbox__label'>{label}</span>
+ </label>
+ </div>
+ );
+ }
+
+ onChange(e) {
+ let {onChange} = this.props;
+ if (onChange) {
+ onChange(e.target.checked);
+ }
+ }
+
+ getChecked() {
+ return this.props.checked;
+ }
+
+ getValue() {
+ return this.props.value;
+ }
+
+}
+
+export default Checkbox;
diff --git a/src/react/Checklist.js b/src/react/Checklist.js
new file mode 100644
index 0000000..1a42aee
--- /dev/null
+++ b/src/react/Checklist.js
@@ -0,0 +1,43 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Checkbox from './Checkbox.js';
+
+const Checklist = ({ items = [], className, onChange }) => (
+ <div className={className}>
+ {items.map((item, index) => {
+ return (
+ <div key={`checkbox-item-${index}`} className='checkbox-item'>
+ <Checkbox
+ key={`${item.label}${index}`}
+ label={item.label}
+ value={item.value}
+ checked={item.checked}
+ disabled={item.disabled}
+ onChange={value => {
+ let obj = {};
+ obj[item.value] = value;
+ onChange(obj);
+ }}
+ data-test-id={item.dataTestId}
+ />
+ </div>
+ );
+ })}
+ </div>
+);
+
+Checklist.propTypes = {
+ items: PropTypes.arrayOf(
+ PropTypes.shape({
+ label: PropTypes.string,
+ value: PropTypes.string,
+ checked: PropTypes.bool,
+ disabled: PropTypes.bool,
+ dataTestId: PropTypes.string
+ })
+ ),
+ className: PropTypes.string,
+ onChange: PropTypes.func
+};
+
+export default Checklist; \ No newline at end of file
diff --git a/src/react/Input.js b/src/react/Input.js
new file mode 100644
index 0000000..5760637
--- /dev/null
+++ b/src/react/Input.js
@@ -0,0 +1,88 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import SVGIcon from './SVGIcon.js';
+
+class Input extends React.Component {
+
+ render() {
+ let {className, disabled, errorMessage, readOnly, label, name, value, type, placeholder, isRequired} = this.props;
+ let dataTestId = this.props['data-test-id'];
+ let inputClasses = `sdc-input__input ${errorMessage ? 'error' : ''} ${readOnly ? 'view-only' : ''}`;
+ let labelClasses = `sdc-input__label ${readOnly ? 'view-only' : ''} ${isRequired ? 'required' : ''}`;
+
+ return (
+ <div className={`sdc-input ${className || ''}`}>
+
+ <label className={labelClasses} htmlFor={name}>{label}</label>
+ <input className={inputClasses}
+ disabled={disabled}
+ readOnly={readOnly}
+ type={type}
+ id={name}
+ name={name}
+ value={this.props.value}
+ placeholder={placeholder}
+ data-test-id={dataTestId}
+ onBlur={(e) => this.onBlur(e)}
+ onKeyDown={(e) => this.onKeyDown(e)}
+ onChange={(e) => this.onChange(e)}/>
+ { errorMessage && <div className="sdc-label__error">
+ <SVGIcon
+ label={errorMessage}
+ labelPosition='right'
+ color='negative'
+ name='exclamationTriangleFull' />
+ </div>}
+ </div>
+ );
+ }
+
+ onChange(e) {
+ let {onChange, readOnly, disabled} = this.props;
+ if (onChange && !readOnly && !disabled) {
+ onChange(e.target.value);
+ }
+ }
+
+ onBlur(e) {
+ let {onBlur, readOnly} = this.props;
+ if (!readOnly && onBlur) {
+ onBlur(e);
+ }
+ }
+
+ onKeyDown(e) {
+ let {onKeyDown, readOnly} = this.props;
+ if (!readOnly && onKeyDown) {
+ onKeyDown(e);
+ }
+ }
+
+ getValue() {
+ return this.props.value;
+ }
+
+}
+Input.propTypes = {
+ name: PropTypes.string,
+ value: PropTypes.string,
+ type: PropTypes.oneOf(['text', 'number']),
+ placeholder : PropTypes.string,
+ onChange: PropTypes.func,
+ onBlur: PropTypes.func,
+ onKeyDown: PropTypes.func,
+ errorMessage: PropTypes.string,
+ readOnly: PropTypes.bool,
+ isRequired: PropTypes.bool,
+ disabled: PropTypes.bool,
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
+ className: PropTypes.string
+};
+
+Input.defaultProps = {
+ type: 'text',
+ readOnly: false,
+ isRequired: false,
+ disabled: false
+};
+export default Input;
diff --git a/src/react/Modal.js b/src/react/Modal.js
new file mode 100644
index 0000000..ab2f7d7
--- /dev/null
+++ b/src/react/Modal.js
@@ -0,0 +1,55 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import Portal from './Portal.js';
+import Body from './ModalBody.js';
+import Header from './ModalHeader.js';
+import Footer from './ModalFooter.js';
+import Title from './ModalTitle.js';
+
+export const modalSize = {
+ medium: 'md',
+ large: 'l',
+ extraLarge: 'xl',
+ small: 'sm',
+ extraSmall: 'xsm'
+};
+
+
+class Modal extends React.Component {
+
+ render() {
+ const {size, type, children, show} = this.props;
+ return (
+ <Portal>
+ <div ref={el => { this.modalRef = el;}}>
+ {show && <div className={`sdc-modal ${modalSize[size]}`}>
+ <div className={`sdc-modal__wrapper sdc-modal-type-${type}`}>
+ {children}
+ </div>
+ </div>}
+ {show && <div className='modal-background' />}
+ </div>
+ </Portal>
+ );
+ }
+}
+
+Modal.defaultProps = {
+ show: false,
+ size: 'medium',
+ type: 'info'
+};
+
+Modal.propTypes = {
+ show: PropTypes.bool,
+ size: PropTypes.string,
+ children: PropTypes.node,
+ type: PropTypes.string
+};
+
+Modal.Body = Body;
+Modal.Header = Header;
+Modal.Footer = Footer;
+Modal.Title = Title;
+export default Modal; \ No newline at end of file
diff --git a/src/react/ModalBody.js b/src/react/ModalBody.js
new file mode 100644
index 0000000..4fae0f6
--- /dev/null
+++ b/src/react/ModalBody.js
@@ -0,0 +1,19 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const ModalBody = ({children, className}) => (
+ <div className={`sdc-modal__content ${className}`} >
+ {children}
+ </div>
+);
+
+ModalBody.propTypes = {
+ children: PropTypes.node,
+ className: PropTypes.string
+};
+
+ModalBody.defaultProps = {
+ className: ''
+};
+
+export default ModalBody; \ No newline at end of file
diff --git a/src/react/ModalFooter.js b/src/react/ModalFooter.js
new file mode 100644
index 0000000..607895d
--- /dev/null
+++ b/src/react/ModalFooter.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Button from './Button.js';
+
+const Footer = ({onClose, closeButtonText, actionButtonText, actionButtonClick, withButtons, children}) => {
+ const closeBtnType = actionButtonClick ? 'secondary' : 'primary';
+ return (
+ <div className='sdc-modal__footer'>
+ {children}
+ {
+ withButtons && <div>
+ {actionButtonClick &&
+ <Button onClick={actionButtonClick}>{actionButtonText}</Button>
+ }
+ <Button btnType={closeBtnType} onClick={onClose}>{closeButtonText}</Button>
+ </div>
+ }
+ </div>
+ );
+};
+
+Footer.propTypes = {
+ onClose: PropTypes.func,
+ closeButtonText: PropTypes.string,
+ actionButtonText: PropTypes.string,
+ actionButtonClick: PropTypes.func,
+ withButtons: PropTypes.bool,
+ children: PropTypes.node
+};
+
+Footer.defaultProps = {
+ closeButtonText: 'Close',
+ withButtons: true
+};
+
+export default Footer; \ No newline at end of file
diff --git a/src/react/ModalHeader.js b/src/react/ModalHeader.js
new file mode 100644
index 0000000..c6be5ef
--- /dev/null
+++ b/src/react/ModalHeader.js
@@ -0,0 +1,41 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import SVGIcon from './SVGIcon.js';
+
+const iconMaper = {
+ error: 'error',
+ info: 'errorCircle',
+ alert: 'exclamationTriangleLine'
+};
+
+const headerTypes = {
+ error: 'sdc-error__header',
+ info: 'sdc-info__header',
+ alert: 'sdc-alert__header',
+ custom: 'sdc-custom__header'
+}
+
+
+
+const Header = ({children, onClose, type}) => (
+ <div className={ headerTypes[type] + ' sdc-modal__header'} >
+ {type !== 'custom'
+ &&
+ <SVGIcon iconClassName='sdc-modal__icon' className='sdc-modal__svg-use' name={iconMaper[type]}/>
+
+ }
+ {children}
+ <SVGIcon iconClassName ='sdc-modal__close-button-svg' className='sdc-modal__close-button' onClick={onClose} name='close'/>
+ </div>
+);
+
+Header.propTypes = {
+ children: PropTypes.node,
+ onClose: PropTypes.func
+};
+
+Header.defaultProps = {
+ type: 'info'
+};
+
+export default Header; \ No newline at end of file
diff --git a/src/react/ModalTitle.js b/src/react/ModalTitle.js
new file mode 100644
index 0000000..b48cc8a
--- /dev/null
+++ b/src/react/ModalTitle.js
@@ -0,0 +1,19 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const Title = ({children, className}) => (
+ <div className={`title ${className}`} >
+ {children}
+ </div>
+);
+
+Title.PropTypes = {
+ children: PropTypes.node,
+ className: PropTypes.string
+};
+
+Title.defaultProps = {
+ className: ''
+};
+
+export default Title; \ No newline at end of file
diff --git a/src/react/Panel.js b/src/react/Panel.js
new file mode 100644
index 0000000..34d2e62
--- /dev/null
+++ b/src/react/Panel.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const Panel = ({ className, children }) => (
+ <div className={`sdc-panel ${className}`}>
+ {children}
+ </div>
+);
+
+Panel.propTypes = {
+ className: PropTypes.string,
+ children: PropTypes.node
+};
+
+Panel.defaultProps = {
+ className: ''
+};
+export default Panel; \ No newline at end of file
diff --git a/src/react/PopupMenu.js b/src/react/PopupMenu.js
new file mode 100644
index 0000000..d2cd29a
--- /dev/null
+++ b/src/react/PopupMenu.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import PopupMenuItem from './PopupMenuItem';
+
+class PopupMenu extends React.Component {
+ render() {
+ const {children = [], onMenuItemClick, position = {}, relative} = this.props;
+ const style = relative ? {left: position.x, top: position.y} : {};
+
+ return (
+ <ul className={`sdc-menu-list ${relative ? 'relative' : ''}`} style={style}>
+ {React.Children.toArray(children).map((child, i) => React.cloneElement(child,
+ {
+ onClick: child.props.onClick || onMenuItemClick,
+ key: i
+ }))}
+ </ul>
+ );
+ }
+}
+
+PopupMenu.propTypes = {
+ relative: PropTypes.bool,
+ position: PropTypes.shape({
+ x: PropTypes.number,
+ y: PropTypes.number
+ }),
+ onMenuItemClick: PropTypes.func
+};
+
+PopupMenu.defaultProps = {
+ relative: false
+};
+
+export const PopupMenuSeparator = () => <li className='separator' />;
+
+PopupMenu.Separator = PopupMenuSeparator;
+PopupMenu.Item = PopupMenuItem;
+export default PopupMenu;
diff --git a/src/react/PopupMenuItem.js b/src/react/PopupMenuItem.js
new file mode 100644
index 0000000..98e3f49
--- /dev/null
+++ b/src/react/PopupMenuItem.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+class PopupMenuItem extends React.Component {
+ render() {
+ const {itemId, value, onClick, selected, disabled} = this.props;
+ const additionalClasses = selected ? 'selected' : disabled ? 'disabled' : '';
+ return (
+ <li
+ className={`sdc-menu-item ${additionalClasses}`}
+ onClick={event => {
+ event.stopPropagation();
+ onClick && !disabled && onClick(itemId);
+ }}>
+ {value}
+ </li>
+ );
+ }
+}
+
+PopupMenuItem.propTypes = {
+ itemId: PropTypes.any,
+ value: PropTypes.any,
+ selected: PropTypes.bool,
+ onClick: PropTypes.func,
+ disabled: PropTypes.bool
+};
+
+PopupMenuItem.defaultProps = {
+ selected: false,
+ disabled: false
+};
+
+export default PopupMenuItem;
diff --git a/src/react/Portal.js b/src/react/Portal.js
new file mode 100644
index 0000000..90e0675
--- /dev/null
+++ b/src/react/Portal.js
@@ -0,0 +1,52 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ReactDOM from 'react-dom';
+
+class Portal extends React.Component {
+ componentDidMount() {
+ this.renderPortal();
+ }
+
+ componentDidUpdate() {
+ this.renderPortal();
+ }
+
+ componentWillUnmount() {
+ if (this.defaultNode) {
+ document.body.removeChild(this.defaultNode);
+ }
+ this.defaultNode = null;
+ this.portal = null;
+ }
+
+ renderPortal() {
+ if (!this.defaultNode) {
+ this.defaultNode = document.createElement('div');
+ this.defaultNode.className = 'onap-sdc-portal';
+ document.body.appendChild(this.defaultNode);
+ }
+
+ let children = this.props.children;
+ if (typeof this.props.children.type === 'function') {
+ children = React.cloneElement(this.props.children);
+ }
+ /**
+ * Change this to ReactDOM.CreatePortal after upgrading to React 16
+ */
+ this.portal = ReactDOM.unstable_renderSubtreeIntoContainer(
+ this,
+ children,
+ this.defaultNode
+ );
+ }
+ render() {
+ return null;
+ }
+
+}
+
+Portal.propTypes = {
+ children: PropTypes.node.isRequired
+};
+
+export default Portal; \ No newline at end of file
diff --git a/src/react/Radio.js b/src/react/Radio.js
new file mode 100644
index 0000000..483521a
--- /dev/null
+++ b/src/react/Radio.js
@@ -0,0 +1,58 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+class Radio extends React.Component {
+ render() {
+ let {checked, disabled, value, label, className, inputRef, name} = this.props;
+ let dataTestId = this.props['data-test-id'];
+ return (
+ <div className={`sdc-radio ${className}`}>
+ <label>
+ <input
+ ref={inputRef}
+ className='sdc-radio__input'
+ value={value}
+ data-test-id={dataTestId}
+ type='radio'
+ name={name}
+ checked={checked}
+ onChange={(e) => this.onChange(e)}
+ disabled={disabled} />
+ <span className='sdc-radio__label'>{label}</span>
+ </label>
+ </div>
+ );
+ }
+
+ onChange(e) {
+ let {onChange} = this.props;
+ if (onChange) {
+ onChange(e.target.checked);
+ }
+ }
+
+ getChecked() {
+ return this.props.checked;
+ }
+
+ getValue() {
+ return this.props.value;
+ }
+}
+
+Radio.propTypes = {
+ checked: PropTypes.bool,
+ value: PropTypes.any,
+ label: PropTypes.string,
+ className: PropTypes.string,
+ inputRef: PropTypes.func,
+ name: PropTypes.string,
+ disabled: PropTypes.bool
+};
+
+Radio.defaultProps = {
+ checked: false,
+ className: ''
+};
+
+export default Radio;
diff --git a/src/react/RadioGroup.js b/src/react/RadioGroup.js
new file mode 100644
index 0000000..59eaca7
--- /dev/null
+++ b/src/react/RadioGroup.js
@@ -0,0 +1,40 @@
+import React from 'react';
+import Radio from './Radio.js';
+
+class RadioGroup extends React.Component {
+ constructor(props) {
+ super(props);
+ this.radios = {};
+ }
+
+ render() {
+ let {name, disabled, title, options, value, className} = this.props;
+ let dataTestId = this.props['data-test-id'];
+ return (<div data-test-id={dataTestId} className={`sdc-radio-group ${className || ''}`}>
+ { title && <label className='sdc-radio-group__legend'>{title}</label> }
+ <div className='sdc-radio-group__radios'>
+ {options.map(option => {
+ let rName = name + '_' + option.value;
+ return (<Radio ref={(radio) => {this.radios[rName] = radio;}} data-test-id={dataTestId + '_' + option.value}
+ key={rName} value={option.value}
+ label={option.label} checked={value === option.value} disabled={disabled}
+ name={name} onChange={() => this.onChange(rName)} />
+ );})}
+ </div>
+ </div>);
+ }
+
+ onChange(rName) {
+ let {onChange} = this.props;
+ let val = this.radios[rName].getValue();
+ if (onChange) {
+ onChange(val);
+ }
+ }
+
+ getValue() {
+ return this.props.value;
+ }
+}
+
+export default RadioGroup;
diff --git a/src/react/SVGIcon.js b/src/react/SVGIcon.js
new file mode 100644
index 0000000..8a5b1ae
--- /dev/null
+++ b/src/react/SVGIcon.js
@@ -0,0 +1,47 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import iconMap from './utils/iconMap.js';
+
+const SVGIcon = ({name, onClick, label, className, iconClassName, labelClassName, labelPosition, color, disabled, ...other}) => {
+
+ let colorClass = (color !== '') ? '__' + color : '';
+ let classes = `svg-icon-wrapper ${iconClassName} ${className} ${colorClass} ${onClick ? 'clickable' : ''} ${labelPosition}`;
+ let camelCasedName = name.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
+ let IconComponent = iconMap[camelCasedName];
+ if (!IconComponent) {
+ console.error('Icon by the name ' + camelCasedName + ' is missing.');
+ }
+
+ return (
+ <div {...other} onClick={onClick} className={classes} disabled={disabled}>
+ { IconComponent && <IconComponent className={`svg-icon __${name}`} /> }
+ { !IconComponent && <span className='svg-icon-missing'>Missing Icon</span> }
+ {label && <span className={`svg-icon-label ${labelClassName}`}>{label}</span>}
+ </div>
+ );
+
+};
+
+SVGIcon.propTypes = {
+ name: PropTypes.string.isRequired,
+ onClick: PropTypes.func,
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
+ labelPosition: PropTypes.string,
+ className: PropTypes.string,
+ iconClassName: PropTypes.string,
+ labelClassName: PropTypes.string,
+ color: PropTypes.string
+};
+
+SVGIcon.defaultProps = {
+ name: '',
+ label: '',
+ className: '',
+ iconClassName: '',
+ labelClassName: '',
+ labelPosition: 'bottom',
+ color: ''
+};
+
+export default SVGIcon;
diff --git a/src/react/Tab.js b/src/react/Tab.js
new file mode 100644
index 0000000..5aa0f16
--- /dev/null
+++ b/src/react/Tab.js
@@ -0,0 +1,20 @@
+import React from 'react';
+
+class Tab extends React.Component {
+ render() {
+ const {activeTab, tabId, title, onClick, disabled, className = ''} = this.props;
+ const dataTestId = this.props['data-test-id'];
+ return (
+ <li
+ className={`sdc-tab ${activeTab === tabId ? 'sdc-tab-active' : ''} ${className}`}
+ onClick={!disabled && onClick}
+ data-test-id={dataTestId}
+ role='tab'
+ disabled={disabled}>
+ {title}
+ </li>
+ );
+ }
+}
+
+export default Tab;
diff --git a/src/react/TabPane.js b/src/react/TabPane.js
new file mode 100644
index 0000000..56a4bf0
--- /dev/null
+++ b/src/react/TabPane.js
@@ -0,0 +1,12 @@
+import React from 'react';
+
+class TabPane extends React.Component {
+ render() {
+ const {children} = this.props;
+ return (<div className='sdc-tab-content' role='tabpanel'>
+ {children}
+ </div>);
+ }
+}
+
+export default TabPane;
diff --git a/src/react/Tabs.js b/src/react/Tabs.js
new file mode 100644
index 0000000..c502038
--- /dev/null
+++ b/src/react/Tabs.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import TabPane from './TabPane.js';
+
+class Tabs extends React.Component {
+ render() {
+ const {type, children = [], activeTab, onTabClick, className} = this.props;
+ return (
+ <div className={type === 'header' ? `sdc-tabs sdc-tabs-header ${className || ''}` : `sdc-tabs sdc-tabs-menu ${className || ''}`} >
+ <ul className='sdc-tabs-list' role='tablist'>
+ {children.map(child => React.cloneElement(child,
+ {
+ key: child.props.tabId,
+ onClick: () => onTabClick(child.props.tabId),
+ activeTab
+ }))}
+ </ul>
+ <TabPane>
+ {children.map(child => {
+ if (child.props.tabId === activeTab) {
+ return child.props.children;
+ }
+ })}
+ </TabPane>
+ </div>
+ );
+ }
+}
+
+export default Tabs;
diff --git a/src/react/Tile.js b/src/react/Tile.js
new file mode 100644
index 0000000..f47f88d
--- /dev/null
+++ b/src/react/Tile.js
@@ -0,0 +1,33 @@
+import React, {Children} from 'react';
+import PropTypes from 'prop-types';
+import TileInfo from './TileInfo.js';
+import TileFooter from './TileFooter.js';
+import SVGIcon from './SVGIcon.js';
+
+const Tile = ({headerText, headerColor, iconName, iconColor, className, onClick, children, dataTestId}) => {
+ let childrenArr = Children.toArray(children);
+ return (
+ <div className={`sdc-tile ${className || ''}`} onClick={onClick} data-test-id={dataTestId}>
+ <div className={`sdc-tile-header ${headerColor || ''}`}>{headerText}</div>
+ <div className='sdc-tile-content'>
+ <div className={`sdc-tile-content-icon ${iconColor || ''}`}>
+ {iconName && <SVGIcon name={iconName}/>}
+ </div>
+ {childrenArr.find(e => e.type === TileInfo)}
+ </div>
+ {childrenArr.find(e => e.type === TileFooter)}
+ </div>
+ );
+};
+
+Tile.propTypes = {
+ headerText: PropTypes.string,
+ headerColor: PropTypes.string,
+ iconName: PropTypes.string,
+ iconColor: PropTypes.string,
+ className: PropTypes.string,
+ onClick: PropTypes.func,
+ dataTestId: PropTypes.string
+};
+
+export default Tile;
diff --git a/src/react/TileFooter.js b/src/react/TileFooter.js
new file mode 100644
index 0000000..3a56908
--- /dev/null
+++ b/src/react/TileFooter.js
@@ -0,0 +1,10 @@
+import React, {Children} from 'react';
+import TileFooterCell from './TileFooterCell.js';
+
+const TileFooter = ({children, align}) => (
+ <div className={`sdc-tile-footer ${align === 'center' ? 'centered' : ''}`}>
+ {Children.toArray(children).filter(e => e.type === TileFooterCell)}
+ </div>
+);
+
+export default TileFooter;
diff --git a/src/react/TileFooterCell.js b/src/react/TileFooterCell.js
new file mode 100644
index 0000000..37e6416
--- /dev/null
+++ b/src/react/TileFooterCell.js
@@ -0,0 +1,7 @@
+import React from 'react';
+
+const TileFooterCell = ({className, children, dataTestId}) => (
+ <span className={`sdc-tile-footer-cell ${className || ''}`} data-test-id={dataTestId}>{children}</span>
+);
+
+export default TileFooterCell;
diff --git a/src/react/TileInfo.js b/src/react/TileInfo.js
new file mode 100644
index 0000000..bda8e74
--- /dev/null
+++ b/src/react/TileInfo.js
@@ -0,0 +1,10 @@
+import React, {Children} from 'react';
+import TileInfoLine from './TileInfoLine.js';
+
+const TileInfo = ({align, children}) => (
+ <div className={`sdc-tile-content-info ${align === 'center' ? 'centered' : ''}`}>
+ {Children.toArray(children).filter(e => e.type === TileInfoLine)}
+ </div>
+);
+
+export default TileInfo;
diff --git a/src/react/TileInfoLine.js b/src/react/TileInfoLine.js
new file mode 100644
index 0000000..5b0e2c9
--- /dev/null
+++ b/src/react/TileInfoLine.js
@@ -0,0 +1,7 @@
+import React from 'react';
+
+const TileInfoLine = ({type, className, children, dataTestId}) => (
+ <div className={`sdc-tile-info-line ${type || ''} ${className || ''}`} data-test-id={dataTestId}>{children}</div>
+);
+
+export default TileInfoLine;
diff --git a/src/react/index.js b/src/react/index.js
new file mode 100644
index 0000000..cbe0161
--- /dev/null
+++ b/src/react/index.js
@@ -0,0 +1,74 @@
+import Accordion from './Accordion.js';
+import Button from './Button.js';
+import Checkbox from './Checkbox.js';
+import Checklist from './Checklist.js';
+import Input from './Input.js';
+import Modal from './Modal.js';
+import ModalBody from './ModalBody.js';
+import ModalFooter from './ModalFooter.js';
+import ModalHeader from './ModalHeader.js';
+import ModalTitle from './ModalTitle.js';
+import Panel from './Panel.js';
+import PopupMenu from './PopupMenu.js';
+import Portal from './Portal.js';
+import Radio from './Radio.js';
+import RadioGroup from './RadioGroup.js';
+import SVGIcon from './SVGIcon.js';
+import Tab from './Tab.js';
+import Tabs from './Tabs.js';
+import Tile from './Tile.js';
+import TileInfo from './TileInfo.js';
+import TileInfoLine from './TileInfoLine.js';
+import TileFooter from './TileFooter.js';
+import TileFooterCell from './TileFooterCell.js';
+
+
+export { Accordion };
+export { Button };
+export { Checkbox };
+export { Checklist };
+export { Input };
+export { Modal };
+export { ModalBody };
+export { ModalFooter };
+export { ModalHeader };
+export { ModalTitle };
+export { Panel };
+export { PopupMenu };
+export { Portal };
+export { Radio };
+export { RadioGroup };
+export { SVGIcon };
+export { Tab };
+export { Tabs };
+export { Tile };
+export { TileInfo };
+export { TileInfoLine };
+export { TileFooter };
+export { TileFooterCell };
+
+export default {
+ Accordion,
+ Button,
+ Checkbox,
+ Checklist,
+ Input,
+ Modal,
+ ModalBody,
+ ModalFooter,
+ ModalHeader,
+ ModalTitle,
+ Panel,
+ PopupMenu,
+ Portal,
+ Radio,
+ RadioGroup,
+ SVGIcon,
+ Tab,
+ Tabs,
+ Tile,
+ TileInfo,
+ TileInfoLine,
+ TileFooter,
+ TileFooterCell
+};