diff --git a/.storybook/stories.js b/.storybook/stories.js
index 46c5c396a..b273675f8 100644
--- a/.storybook/stories.js
+++ b/.storybook/stories.js
@@ -7,13 +7,15 @@
export default [
'Introduction',
- // 1. Foundations
+ // Foundations
'Colors',
'Grid',
'Typography',
'Icon',
- // 2. Buttons
+ 'Alert',
+
+ // Buttons
'Button',
// Forms
@@ -26,18 +28,24 @@ export default [
'Slider',
'TextArea',
- // 8. Tooltip
+ // Tooltip
'Tooltip',
- // 15. Badge
+ // Badge
'Badge',
- // 16. Tags
+ // Tags
'Tag',
- // 17. Cards
+ // TabbedView
+ 'TabbedView',
+
+ // Card
'Card',
- // 18. SnackBar
+ // Modal
+ 'Modal',
+
+ // SnackBar
'SnackBar',
].map(s => `./${s}/${s}.story.jsx`);
diff --git a/components/Alert/Alert.jsx b/components/Alert/Alert.jsx
new file mode 100644
index 000000000..21e179d91
--- /dev/null
+++ b/components/Alert/Alert.jsx
@@ -0,0 +1,91 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import styled from 'styled-components';
+import Button from '../Button';
+import Colors from '../Colors';
+import Icon from '../Icon';
+
+const BORDER_SIZE = '1.5px';
+const DEFAULT_SPACING = '16px';
+const WRAPPER_SPACING = '11px';
+
+const Content = styled.div`
+ align-items: start;
+ display: flex;
+`;
+
+const AlertIcon = styled(Icon)``;
+
+const CloseButton = styled(Button.Icon).attrs({
+ icon: 'close',
+})`
+ height: auto;
+ width: auto;
+ margin: 0 0 0 ${DEFAULT_SPACING};
+ padding: 0;
+ opacity: 0.8;
+ transition: opacity 0.4s ease;
+
+ :hover {
+ background: none;
+ opacity: 1;
+ }
+`;
+
+CloseButton.displayName = 'CloseButton';
+
+const getStylesBySkin = skin => {
+ const colorName = skin.toUpperCase();
+ const colorSchema = Colors[colorName];
+
+ return `
+ color: ${colorSchema[900] ? colorSchema[900] : 'inherit'};
+ background-color: ${colorSchema[200]};
+ border: ${BORDER_SIZE} solid ${colorSchema[500]};
+
+ ${Content} ${AlertIcon} {
+ color: ${colorSchema[500]};
+ margin-right: ${DEFAULT_SPACING};
+ }
+
+ ${Content} ${CloseButton} {
+ color: ${colorSchema[500]};
+ }
+ `;
+};
+
+const Wrapper = styled.div`
+ border-radius: 8px;
+ box-sizing: border-box;
+ padding: ${WRAPPER_SPACING} ${DEFAULT_SPACING};
+
+ ${({ skin }) => getStylesBySkin(skin)}
+`;
+
+const Alert = ({ icon, children, onClose, ...rest }) => (
+
+
+ {icon && }
+ {children && {children}}
+ {onClose && }
+
+
+);
+
+Alert.defaultProps = {
+ icon: null,
+ skin: 'blue',
+};
+
+Alert.propTypes = {
+ /** At least one children is required for Alert component properly works */
+ children: PropTypes.node.isRequired,
+ /** Icon name. The full catalogue can be found
+ * [here](/?selectedKind=1.%20Foundation&selectedStory=Icons) */
+ icon: PropTypes.string,
+ /** You must pass a callback that is called when close button is clicked */
+ onClose: PropTypes.func.isRequired,
+ skin: PropTypes.oneOf(['blue', 'success', 'warning', 'error']),
+};
+
+export default Alert;
diff --git a/components/Alert/Alert.unit.test.jsx b/components/Alert/Alert.unit.test.jsx
new file mode 100644
index 000000000..34a59a6d3
--- /dev/null
+++ b/components/Alert/Alert.unit.test.jsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import renderer from 'react-test-renderer';
+import Alert from './Alert';
+
+describe('Alert component', () => {
+ it('Should match the snapshot of a simple alert', () => {
+ const simpleAlert = {}}>Sample alert;
+ expect(renderer.create(simpleAlert).toJSON()).toMatchSnapshot();
+ });
+
+ describe('When you set a alert custom icon', () => {
+ it('Should match the snapshot with an icon', () => {
+ const simpleAlert = (
+ {}} icon="info">
+ Sample alert
+
+ );
+ expect(renderer.create(simpleAlert).toJSON()).toMatchSnapshot();
+ });
+ });
+
+ describe('When you set a different skin', () => {
+ it('Should match a skin snapshot', () => {
+ const blue = (
+ {}} skin="blue">
+ blue
+
+ );
+ const success = (
+ {}} skin="success">
+ success
+
+ );
+ const warning = (
+ {}} skin="warning">
+ warning
+
+ );
+ const error = (
+ {}} skin="error">
+ error
+
+ );
+
+ expect(renderer.create(blue).toJSON()).toMatchSnapshot();
+ expect(renderer.create(success).toJSON()).toMatchSnapshot();
+ expect(renderer.create(warning).toJSON()).toMatchSnapshot();
+ expect(renderer.create(error).toJSON()).toMatchSnapshot();
+ });
+ });
+});
diff --git a/components/Alert/__snapshots__/Alert.unit.test.jsx.snap b/components/Alert/__snapshots__/Alert.unit.test.jsx.snap
new file mode 100644
index 000000000..9f16163d9
--- /dev/null
+++ b/components/Alert/__snapshots__/Alert.unit.test.jsx.snap
@@ -0,0 +1,1062 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Alert component Should match the snapshot of a simple alert 1`] = `
+.c7 {
+ pointer-events: none;
+}
+
+.c5 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ font-weight: bold;
+ -webkit-letter-spacing: 0.2px;
+ -moz-letter-spacing: 0.2px;
+ -ms-letter-spacing: 0.2px;
+ letter-spacing: 0.2px;
+ font-size: 16px;
+ padding: 0 16px;
+ height: 40px;
+ -webkit-transition: all 0.2s ease-in-out;
+ transition: all 0.2s ease-in-out;
+ cursor: pointer;
+ background-color: #1355d0;
+ border: 1.5px solid #1355d0;
+ box-shadow: 0 2px 4px 0 #cccccc;
+ color: #FFFFFF;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ border-radius: 4px;
+}
+
+.c5 *:nth-child(2) {
+ margin-left: 5px;
+}
+
+.c5 .c6 {
+ font-size: 24px;
+}
+
+.c5:hover {
+ box-shadow: 0 2px 4px 0 #cccccc;
+ background-color: #002f7b;
+ border-color: #002f7b;
+ color: #FFFFFF;
+}
+
+.c5:focus {
+ box-shadow: 0 2px 6px 0 rgba(19,85,208,0.5);
+ background-color: #1355d0;
+ border-color: #1355d0;
+ color: #FFFFFF;
+}
+
+.c5:active {
+ box-shadow: 0 2px 4px 0 #4c4c4c;
+ background-color: #002f7b;
+ border-color: #002f7b;
+ color: #FFFFFF;
+}
+
+.c2 {
+ -webkit-align-items: start;
+ -webkit-box-align: start;
+ -ms-flex-align: start;
+ align-items: start;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+}
+
+.c4 {
+ border-radius: 50%;
+ border: none;
+ color: rgba(128,128,128,0.5);
+ width: 40px;
+ background-color: transparent;
+ box-shadow: none;
+ outline: none;
+ height: auto;
+ width: auto;
+ margin: 0 0 0 16px;
+ padding: 0;
+ opacity: 0.8;
+ -webkit-transition: opacity 0.4s ease;
+ transition: opacity 0.4s ease;
+}
+
+.c4:hover,
+.c4:focus {
+ box-shadow: none;
+ background-color: rgba(204,204,204,0.4);
+ color: #4c4c4c;
+}
+
+.c4:active {
+ box-shadow: none;
+ background-color: rgba(128,128,128,0.5);
+ color: #4c4c4c;
+}
+
+.c4:hover {
+ background: none;
+ opacity: 1;
+}
+
+.c0 {
+ border-radius: 8px;
+ box-sizing: border-box;
+ padding: 11px 16px;
+ color: inherit;
+ background-color: #e5edfc;
+ border: 1.5px solid #1355d0;
+}
+
+.c0 .c1 .c8 {
+ color: #1355d0;
+ margin-right: 16px;
+}
+
+.c0 .c1 .c3 {
+ color: #1355d0;
+}
+
+
query[breakpoint]`width: ${value};`,
+ );
+}
+
+const ModalCard = styled(Card)`
+ ${Card.Header} {
+ padding-right: 56px;
+ }
+
+ ${getBreakpoint}
+`;
+
+const CloseIcon = styled(Button.Icon).attrs({
+ icon: 'close',
+})`
+ position: absolute;
+ top: 16px;
+ right: 16px;
+`;
+
+CloseIcon.displayName = 'CloseIcon';
+
+const ModalWrapper = styled.div`
+ align-items: center;
+ background-color: ${Colors.SHADOW[50]};
+ display: flex;
+ height: 100vh;
+ justify-content: center;
+ position: fixed;
+ top: 0;
+ width: 100vw;
+`;
+
+ModalWrapper.displayName = 'ModalWrapper';
+
+class Modal extends React.Component {
+ static Header = Header;
+
+ static HeaderText = HeaderText;
+
+ static Content = Content;
+
+ static Title = Title;
+
+ static Footer = Footer;
+
+ constructor(props) {
+ super(props);
+
+ this.modalWrapperRef = React.createRef();
+ this.modalOverlay = document.createElement('section');
+ this.focusableElements = [];
+ this.focusedElementBeforeOpen = document.activeElement;
+ this.firstFocusableElement = null;
+ this.lastFocusableElement = null;
+ }
+
+ componentDidMount() {
+ const { body } = document;
+
+ body.appendChild(this.modalOverlay);
+
+ this.focusableElements = this.modalOverlay.querySelectorAll(
+ `a[href],
+ area[href],
+ input:not([disabled]),
+ select:not([disabled]),
+ textarea:not([disabled]),
+ button:not([disabled]),
+ [tabindex="0"]`,
+ );
+ this.firstFocusableElement = this.focusableElements[0]; // eslint-disable-line
+ this.lastFocusableElement = this.focusableElements[
+ this.focusableElements.length - 1
+ ];
+ this.firstFocusableElement.focus();
+
+ window.addEventListener('keydown', this.handleKeyDown);
+ window.addEventListener('keydown', this.handleEscKey);
+ }
+
+ componentWillUnmount() {
+ const { body } = document;
+ this.focusedElementBeforeOpen.focus();
+
+ body.removeChild(this.modalOverlay);
+ window.removeEventListener('keydown', this.handleKeyDown);
+ window.removeEventListener('keydown', this.handleEscKey);
+ }
+
+ handleClickOutside = ({ target }) => {
+ const { onClose } = this.props;
+ const { current: modalRef } = this.modalWrapperRef;
+
+ if (target === modalRef) {
+ onClose();
+ }
+ };
+
+ handleEscKey = ({ key }) => {
+ const { onClose } = this.props;
+
+ if (key === 'Escape') {
+ onClose();
+ }
+ };
+
+ handleBackwardTab = e => {
+ if (document.activeElement === this.firstFocusableElement) {
+ e.preventDefault();
+ this.lastFocusableElement.focus();
+ }
+ };
+
+ handleFowardTab = e => {
+ if (document.activeElement === this.lastFocusableElement) {
+ e.preventDefault();
+ this.firstFocusableElement.focus();
+ }
+ };
+
+ handleKeyDown = e => {
+ if (e.key === 'Tab') {
+ if (this.focusableElements.length === 1) {
+ return e.preventDefault();
+ }
+
+ if (e.shiftKey) {
+ return this.handleBackwardTab(e);
+ }
+
+ return this.handleFowardTab(e);
+ }
+
+ return false;
+ };
+
+ render() {
+ const { children, onClose, closeButtonAriaLabel } = this.props;
+
+ return ReactDOM.createPortal(
+
+
+ {children}
+
+
+ ,
+ this.modalOverlay,
+ );
+ }
+}
+
+Modal.propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ ]),
+ /** Function to be called when close icon is clicked. */
+ onClose: PropTypes.func,
+ /** aria-label property value for the close button icon. */
+ closeButtonAriaLabel: PropTypes.string,
+};
+
+Modal.defaultProps = {
+ children: undefined,
+ onClose: () => {},
+ closeButtonAriaLabel: 'close dialog',
+};
+
+export default Modal;
diff --git a/components/Modal/Modal.unit.test.jsx b/components/Modal/Modal.unit.test.jsx
new file mode 100644
index 000000000..6e285df50
--- /dev/null
+++ b/components/Modal/Modal.unit.test.jsx
@@ -0,0 +1,103 @@
+import React from 'react';
+import { mount } from 'enzyme';
+import toJson from 'enzyme-to-json';
+import Modal from './Modal';
+
+describe('
', () => {
+ let component;
+ const wrapper = (
+
+
+
+ Modal title
+
+
+ Modal Content
+ ModalFooter
+
+ );
+
+ afterEach(() => {
+ if (component && component.length) component.unmount();
+ });
+
+ describe('Snapshots', () => {
+ it('should match the snapshot', () => {
+ component = mount(wrapper);
+ expect(toJson(component)).toMatchSnapshot();
+ });
+ });
+
+ describe('Modal DOM position', () => {
+ it('should be child of body element', () => {
+ expect(document.body.childNodes.length).toBe(0);
+ component = mount(wrapper);
+ expect(document.body.childNodes.length).toBe(1);
+ });
+ });
+
+ describe('closeIcon', () => {
+ it('should exists a closeIcon button when modal is rendered', () => {
+ const modal = mount(
);
+
+ expect(modal.find('CloseIcon')).toBeTruthy();
+ });
+ });
+
+ describe('onClose prop', () => {
+ it('should call onClose when CloseIcon is clicked', () => {
+ const onCloseMock = jest.fn();
+ const modal = mount(
);
+
+ modal.find('CloseIcon').simulate('click');
+
+ expect(onCloseMock).toHaveBeenCalled();
+ });
+
+ it('should call onClose when is clicked outside Modal', () => {
+ const onCloseMock = jest.fn();
+ const modal = mount(
);
+
+ modal.find('ModalWrapper').simulate('click');
+
+ expect(onCloseMock).toHaveBeenCalled();
+ });
+
+ it('should call onClose when "Escape" key is pressed', () => {
+ const eventMap = {
+ keydown: null,
+ };
+ window.addEventListener = jest.fn((event, cb) => {
+ eventMap[event] = cb;
+ });
+
+ const onCloseMock = jest.fn();
+ mount(
);
+
+ eventMap.keydown({ key: 'Escape' });
+
+ expect(onCloseMock).toHaveBeenCalled();
+ });
+ });
+
+ describe('Tab events', () => {
+ it('should focus first element when Modal is opened', () => {
+ const modal = mount(
+
+
+
+
+
+ ,
+ );
+
+ const focusedElement = document.activeElement;
+ expect(
+ modal
+ .find('button')
+ .at(0)
+ .getDOMNode(),
+ ).toBe(focusedElement);
+ });
+ });
+});
diff --git a/components/Modal/__snapshots__/Modal.unit.test.jsx.snap b/components/Modal/__snapshots__/Modal.unit.test.jsx.snap
new file mode 100644
index 000000000..3a658ea62
--- /dev/null
+++ b/components/Modal/__snapshots__/Modal.unit.test.jsx.snap
@@ -0,0 +1,1149 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`
Snapshots should match the snapshot 1`] = `
+.c5 {
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+}
+
+.c7 {
+ font-size: 24px;
+ font-weight: 600;
+ margin: 0;
+}
+
+.c9 {
+ padding: 0 20px 20px;
+}
+
+.c2 {
+ background-color: #FFFFFF;
+ border-radius: 8px;
+ box-shadow: 0 2px 6px 0 rgba(128,128,128,0.5);
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ position: relative;
+}
+
+.c13 {
+ pointer-events: none;
+}
+
+.c11 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ font-weight: bold;
+ -webkit-letter-spacing: 0.2px;
+ -moz-letter-spacing: 0.2px;
+ -ms-letter-spacing: 0.2px;
+ letter-spacing: 0.2px;
+ font-size: 16px;
+ padding: 0 16px;
+ height: 40px;
+ -webkit-transition: all 0.2s ease-in-out;
+ transition: all 0.2s ease-in-out;
+ cursor: pointer;
+ background-color: #1355d0;
+ border: 1.5px solid #1355d0;
+ box-shadow: 0 2px 4px 0 #cccccc;
+ color: #FFFFFF;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ border-radius: 4px;
+}
+
+.c11 *:nth-child(2) {
+ margin-left: 5px;
+}
+
+.c11 .c12 {
+ font-size: 24px;
+}
+
+.c11:hover {
+ box-shadow: 0 2px 4px 0 #cccccc;
+ background-color: #002f7b;
+ border-color: #002f7b;
+ color: #FFFFFF;
+}
+
+.c11:focus {
+ box-shadow: 0 2px 6px 0 rgba(19,85,208,0.5);
+ background-color: #1355d0;
+ border-color: #1355d0;
+ color: #FFFFFF;
+}
+
+.c11:active {
+ box-shadow: 0 2px 4px 0 #4c4c4c;
+ background-color: #002f7b;
+ border-color: #002f7b;
+ color: #FFFFFF;
+}
+
+.c8 {
+ padding: 12px 20px;
+ font-size: 14px;
+ margin: 0;
+ font-size: 16px;
+ max-height: 70vh;
+ overflow-y: auto;
+ padding: 16px 24px;
+}
+
+.c4 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ padding: 20px 20px 0;
+ padding: 24px 24px 0;
+}
+
+.c4 > * {
+ margin-right: 18px;
+}
+
+.c4 > *:last-child {
+ margin-right: 0;
+}
+
+.c6 {
+ font-weight: 700;
+ line-height: 1.25;
+}
+
+.c1 .c3 {
+ padding-right: 56px;
+}
+
+.c10 {
+ border-radius: 50%;
+ border: none;
+ color: rgba(128,128,128,0.5);
+ width: 40px;
+ background-color: transparent;
+ box-shadow: none;
+ outline: none;
+ position: absolute;
+ top: 16px;
+ right: 16px;
+}
+
+.c10:hover,
+.c10:focus {
+ box-shadow: none;
+ background-color: rgba(204,204,204,0.4);
+ color: #4c4c4c;
+}
+
+.c10:active {
+ box-shadow: none;
+ background-color: rgba(128,128,128,0.5);
+ color: #4c4c4c;
+}
+
+.c0 {
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ background-color: rgba(128,128,128,0.5);
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ height: 100vh;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ position: fixed;
+ top: 0;
+ width: 100vw;
+}
+
+@media (min-width:1px) {
+ .c1 {
+ width: 90%;
+ }
+}
+
+@media (min-width:600px) {
+ .c1 {
+ width: 400px;
+ }
+}
+
+@media (min-width:1024px) {
+ .c1 {
+ width: 600px;
+ }
+}
+
+@media (min-width:1440px) {
+ .c1 {
+ width: 800px;
+ }
+}
+
+
+ * {
+ margin-right: 18px;
+}
+
+.c4 > *:last-child {
+ margin-right: 0;
+}
+
+.c6 {
+ font-weight: 700;
+ line-height: 1.25;
+}
+
+.c1 .c3 {
+ padding-right: 56px;
+}
+
+.c10 {
+ border-radius: 50%;
+ border: none;
+ color: rgba(128,128,128,0.5);
+ width: 40px;
+ background-color: transparent;
+ box-shadow: none;
+ outline: none;
+ position: absolute;
+ top: 16px;
+ right: 16px;
+}
+
+.c10:hover,
+.c10:focus {
+ box-shadow: none;
+ background-color: rgba(204,204,204,0.4);
+ color: #4c4c4c;
+}
+
+.c10:active {
+ box-shadow: none;
+ background-color: rgba(128,128,128,0.5);
+ color: #4c4c4c;
+}
+
+.c0 {
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ background-color: rgba(128,128,128,0.5);
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ height: 100vh;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ position: fixed;
+ top: 0;
+ width: 100vw;
+}
+
+@media (min-width:1px) {
+ .c1 {
+ width: 90%;
+ }
+}
+
+@media (min-width:600px) {
+ .c1 {
+ width: 400px;
+ }
+}
+
+@media (min-width:1024px) {
+ .c1 {
+ width: 600px;
+ }
+}
+
+@media (min-width:1440px) {
+ .c1 {
+ width: 800px;
+ }
+}
+
+
+
+
+
+
+ Modal Content
+
+
+
+
+
+
+ }
+ >
+
+
+
+
+
+ Modal Content
+
+
+
+
+ ,
+ }
+ }
+ onClick={[Function]}
+ role="dialog"
+ >
+ title && children;
+
+Tab.propTypes = {
+ title: PropTypes.string.isRequired,
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ ]).isRequired,
+};
+
+Tab.displayName = 'Tab';
+
+export default Tab;
diff --git a/components/TabbedView/TabbedView.jsx b/components/TabbedView/TabbedView.jsx
new file mode 100644
index 000000000..b0d7fe4ec
--- /dev/null
+++ b/components/TabbedView/TabbedView.jsx
@@ -0,0 +1,160 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import styled, { css } from 'styled-components';
+import Colors from '../Colors';
+import Tab from './Tab';
+
+const getColors = skin => {
+ const skins = {
+ default: {
+ activeText: Colors.BLUE[500],
+ background: 'transparent',
+ hoverBackground: Colors.BLUE[200],
+ text: 'inherit',
+ },
+ blue: {
+ activeText: Colors.WHITE,
+ background: Colors.BLUE[500],
+ hoverBackground: Colors.COBALT[500],
+ text: Colors.WHITE,
+ },
+ };
+
+ return skins[skin] || skins.default;
+};
+
+const Navbar = styled.nav.attrs({
+ role: 'tablist',
+})`
+ display: flex;
+ flex-grow: 1;
+ flex-shrink: 1;
+ margin: 0 0 25px 0;
+ padding: 0;
+`;
+
+Navbar.displayName = 'Navbar';
+
+const NavItem = styled.button.attrs({
+ role: 'tab',
+})`
+ border: none;
+ box-sizing: border-box;
+ cursor: pointer;
+ flex-shrink: 0;
+ font-size: 20px;
+ height: 48px;
+ line-height: 1.5;
+ min-width: 90px;
+ outline: none;
+ overflow: hidden;
+ padding: 9px 16px;
+ transition: all 0.2s ease-in-out;
+ text-align: center;
+ text-transform: uppercase;
+
+ ${({ skin }) => {
+ const { background, text, activeText, hoverBackground } = getColors(skin);
+ return css`
+ background-color: ${background};
+ color: ${text};
+
+ &[aria-selected='true'] {
+ border-bottom: 4px solid ${activeText};
+ color: ${activeText};
+ cursor: default;
+ font-weight: bold;
+ }
+
+ &:hover {
+ background-color: ${hoverBackground};
+ }
+ `;
+ }}
+`;
+
+NavItem.displayName = 'NavItem';
+
+const RenderIf = ({ conditional, children }) => conditional && children;
+
+class TabbedView extends React.Component {
+ static Tab = Tab;
+
+ constructor(props) {
+ super(props);
+
+ const { children, activeTab } = props;
+
+ if (activeTab) {
+ this.state = { activeTab };
+ } else {
+ const [
+ {
+ props: { title },
+ },
+ ] = React.Children.toArray(children);
+ this.state = { activeTab: title };
+ }
+ }
+
+ onTabClick = tab => {
+ this.setState({ activeTab: tab });
+ };
+
+ render() {
+ const { children, skin } = this.props;
+ const { activeTab } = this.state;
+
+ return (
+ <>
+
+ {React.Children.map(children, ({ props: { title } }) => (
+ this.onTabClick(title)}
+ skin={skin}
+ id={`${title}-tab`}
+ aria-controls={`${title}-panel`}
+ aria-selected={title === activeTab}
+ >
+ {title}
+
+ ))}
+
+
+ {React.Children.map(
+ children,
+ ({ props: { title, children: tabContent } }) => (
+
+
+ {tabContent}
+
+
+ ),
+ )}
+ >
+ );
+ }
+}
+
+TabbedView.propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ ]).isRequired,
+ activeTab: PropTypes.string,
+ skin: PropTypes.oneOf(['default', 'blue']),
+};
+
+TabbedView.defaultProps = {
+ activeTab: undefined,
+ skin: 'default',
+};
+
+TabbedView.displayName = 'TabbedView';
+
+export default TabbedView;
diff --git a/components/TabbedView/TabbedView.unit.test.jsx b/components/TabbedView/TabbedView.unit.test.jsx
new file mode 100644
index 000000000..2ced232f0
--- /dev/null
+++ b/components/TabbedView/TabbedView.unit.test.jsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import TabbedView from './TabbedView';
+import Tab from './Tab';
+
+describe('
', () => {
+ describe('Snapshot', () => {
+ it('should match snapshot', () => {
+ const component = shallow(
+
+
+ Example text
+
+ ,
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+ });
+
+ describe('Active Tab', () => {
+ const component = shallow(
+
+ Candidatos content
+ Empresas content
+ Educação content
+ ,
+ );
+
+ it('should have first Tab active', () => {
+ expect(component.html()).toContain('Candidatos content');
+ expect(component.html()).not.toContain('Empresas content');
+ expect(component.html()).not.toContain('Educação content');
+ });
+
+ it('should change active tab when another Tab is clicked', () => {
+ const secondNavItem = component.find('NavItem').at(1);
+
+ secondNavItem.simulate('click');
+
+ expect(component.html()).not.toContain('Candidatos content');
+ expect(component.html()).toContain('Empresas content');
+ expect(component.html()).not.toContain('Educação content');
+ });
+
+ it('should have pre selected Tab', () => {
+ const wrapper = shallow(
+
+ Candidatos content
+ Empresas content
+ Educação content
+ ,
+ );
+
+ expect(wrapper.html()).not.toContain('Candidatos content');
+ expect(wrapper.html()).not.toContain('Empresas content');
+ expect(wrapper.html()).toContain('Educação content');
+ });
+ });
+});
diff --git a/components/TabbedView/__snapshots__/TabbedView.unit.test.jsx.snap b/components/TabbedView/__snapshots__/TabbedView.unit.test.jsx.snap
new file mode 100644
index 000000000..1c0f0ff2f
--- /dev/null
+++ b/components/TabbedView/__snapshots__/TabbedView.unit.test.jsx.snap
@@ -0,0 +1,387 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`
Snapshot should match snapshot 1`] = `
+ShallowWrapper {
+ Symbol(enzyme.__root__): [Circular],
+ Symbol(enzyme.__unrendered__):
+
+
+ Example text
+
+
+ ,
+ Symbol(enzyme.__renderer__): Object {
+ "batchedUpdates": [Function],
+ "checkPropTypes": [Function],
+ "getNode": [Function],
+ "render": [Function],
+ "simulateError": [Function],
+ "simulateEvent": [Function],
+ "unmount": [Function],
+ },
+ Symbol(enzyme.__node__): Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "function",
+ "props": Object {
+ "children": Array [
+
+
+ example
+
+ ,
+ Array [
+
+
+ ,
+ ],
+ ],
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "function",
+ "props": Object {
+ "children": Array [
+
+ example
+ ,
+ ],
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": "example/.0",
+ "nodeType": "function",
+ "props": Object {
+ "aria-controls": "example-panel",
+ "aria-selected": true,
+ "children": "example",
+ "id": "example-tab",
+ "onClick": [Function],
+ "skin": "default",
+ },
+ "ref": null,
+ "rendered": "example",
+ "type": Object {
+ "$$typeof": Symbol(react.forward_ref),
+ "attrs": Array [
+ Object {
+ "role": "tab",
+ },
+ ],
+ "componentStyle": ComponentStyle {
+ "componentId": "TabbedView__NavItem-sc-131sgnr-1",
+ "isStatic": false,
+ "rules": Array [
+ "border:none;box-sizing:border-box;cursor:pointer;flex-shrink:0;font-size:20px;height:48px;line-height:1.5;min-width:90px;outline:none;overflow:hidden;padding:9px 16px;transition:all 0.2s ease-in-out;text-align:center;text-transform:uppercase;",
+ [Function],
+ ],
+ },
+ "displayName": "NavItem",
+ "foldedComponentIds": Array [],
+ "render": [Function],
+ "styledComponentId": "TabbedView__NavItem-sc-131sgnr-1",
+ "target": "button",
+ "toString": [Function],
+ "warnTooManyClasses": [Function],
+ "withComponent": [Function],
+ },
+ },
+ ],
+ "type": Object {
+ "$$typeof": Symbol(react.forward_ref),
+ "attrs": Array [
+ Object {
+ "role": "tablist",
+ },
+ ],
+ "componentStyle": ComponentStyle {
+ "componentId": "TabbedView__Navbar-sc-131sgnr-0",
+ "isStatic": true,
+ "rules": Array [
+ "display:flex;flex-grow:1;flex-shrink:1;margin:0 0 25px 0;padding:0;",
+ ],
+ },
+ "displayName": "Navbar",
+ "foldedComponentIds": Array [],
+ "render": [Function],
+ "styledComponentId": "TabbedView__Navbar-sc-131sgnr-0",
+ "target": "nav",
+ "toString": [Function],
+ "warnTooManyClasses": [Function],
+ "withComponent": [Function],
+ },
+ },
+ Object {
+ "instance": null,
+ "key": ".0",
+ "nodeType": "function",
+ "props": Object {
+ "children":
,
+ "conditional": true,
+ },
+ "ref": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "host",
+ "props": Object {
+ "aria-labelledby": "example-tab",
+ "children":
+ Example text
+
,
+ "id": "example-panel",
+ "role": "tabpanel",
+ },
+ "ref": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "host",
+ "props": Object {
+ "children": "Example text",
+ },
+ "ref": null,
+ "rendered": "Example text",
+ "type": "p",
+ },
+ "type": "div",
+ },
+ "type": [Function],
+ },
+ ],
+ "type": Symbol(react.fragment),
+ },
+ Symbol(enzyme.__nodes__): Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "function",
+ "props": Object {
+ "children": Array [
+
+
+ example
+
+ ,
+ Array [
+
+
+ ,
+ ],
+ ],
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "function",
+ "props": Object {
+ "children": Array [
+
+ example
+ ,
+ ],
+ },
+ "ref": null,
+ "rendered": Array [
+ Object {
+ "instance": null,
+ "key": "example/.0",
+ "nodeType": "function",
+ "props": Object {
+ "aria-controls": "example-panel",
+ "aria-selected": true,
+ "children": "example",
+ "id": "example-tab",
+ "onClick": [Function],
+ "skin": "default",
+ },
+ "ref": null,
+ "rendered": "example",
+ "type": Object {
+ "$$typeof": Symbol(react.forward_ref),
+ "attrs": Array [
+ Object {
+ "role": "tab",
+ },
+ ],
+ "componentStyle": ComponentStyle {
+ "componentId": "TabbedView__NavItem-sc-131sgnr-1",
+ "isStatic": false,
+ "rules": Array [
+ "border:none;box-sizing:border-box;cursor:pointer;flex-shrink:0;font-size:20px;height:48px;line-height:1.5;min-width:90px;outline:none;overflow:hidden;padding:9px 16px;transition:all 0.2s ease-in-out;text-align:center;text-transform:uppercase;",
+ [Function],
+ ],
+ },
+ "displayName": "NavItem",
+ "foldedComponentIds": Array [],
+ "render": [Function],
+ "styledComponentId": "TabbedView__NavItem-sc-131sgnr-1",
+ "target": "button",
+ "toString": [Function],
+ "warnTooManyClasses": [Function],
+ "withComponent": [Function],
+ },
+ },
+ ],
+ "type": Object {
+ "$$typeof": Symbol(react.forward_ref),
+ "attrs": Array [
+ Object {
+ "role": "tablist",
+ },
+ ],
+ "componentStyle": ComponentStyle {
+ "componentId": "TabbedView__Navbar-sc-131sgnr-0",
+ "isStatic": true,
+ "rules": Array [
+ "display:flex;flex-grow:1;flex-shrink:1;margin:0 0 25px 0;padding:0;",
+ ],
+ },
+ "displayName": "Navbar",
+ "foldedComponentIds": Array [],
+ "render": [Function],
+ "styledComponentId": "TabbedView__Navbar-sc-131sgnr-0",
+ "target": "nav",
+ "toString": [Function],
+ "warnTooManyClasses": [Function],
+ "withComponent": [Function],
+ },
+ },
+ Object {
+ "instance": null,
+ "key": ".0",
+ "nodeType": "function",
+ "props": Object {
+ "children":
,
+ "conditional": true,
+ },
+ "ref": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "host",
+ "props": Object {
+ "aria-labelledby": "example-tab",
+ "children":
+ Example text
+
,
+ "id": "example-panel",
+ "role": "tabpanel",
+ },
+ "ref": null,
+ "rendered": Object {
+ "instance": null,
+ "key": undefined,
+ "nodeType": "host",
+ "props": Object {
+ "children": "Example text",
+ },
+ "ref": null,
+ "rendered": "Example text",
+ "type": "p",
+ },
+ "type": "div",
+ },
+ "type": [Function],
+ },
+ ],
+ "type": Symbol(react.fragment),
+ },
+ ],
+ Symbol(enzyme.__options__): Object {
+ "adapter": ReactSixteenAdapter {
+ "options": Object {
+ "enableComponentDidUpdateOnSetState": true,
+ "legacyContextMode": "parent",
+ "lifecycles": Object {
+ "componentDidUpdate": Object {
+ "onSetState": true,
+ },
+ "getChildContext": Object {
+ "calledByRenderer": false,
+ },
+ "getDerivedStateFromProps": Object {
+ "hasShouldComponentUpdateBug": false,
+ },
+ "getSnapshotBeforeUpdate": true,
+ "setState": Object {
+ "skipsComponentDidUpdateOnNullish": true,
+ },
+ },
+ },
+ },
+ },
+ Symbol(enzyme.__childContext__): null,
+}
+`;
diff --git a/components/TabbedView/index.js b/components/TabbedView/index.js
new file mode 100644
index 000000000..3439e51e0
--- /dev/null
+++ b/components/TabbedView/index.js
@@ -0,0 +1,4 @@
+import TabbedView from './TabbedView';
+import Tab from './Tab';
+
+export { Tab, TabbedView };
diff --git a/components/Tooltip/Tooltip.jsx b/components/Tooltip/Tooltip.jsx
index c124c32c3..792185467 100644
--- a/components/Tooltip/Tooltip.jsx
+++ b/components/Tooltip/Tooltip.jsx
@@ -4,8 +4,6 @@ import styled from 'styled-components';
import Colors from '../Colors';
import placementConfig from './options';
-const TIP_MAXLENGTH = 36;
-
const Tip = styled.div`
background-color: ${Colors.BLACK[700]};
border-color: ${Colors.BLACK[700]};
@@ -13,96 +11,62 @@ const Tip = styled.div`
color: ${Colors.WHITE};
font-size: 16px;
font-weight: bold;
- opacity: ${props => (props.show ? '1' : '0')};
+ opacity: ${({ visible }) => (visible ? '1' : '0')};
padding: 4px 8px;
position: absolute;
+ line-height: 0;
text-align: center;
transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out;
- visibility: ${props => (props.show ? 'visible' : 'hidden')};
z-index: 100;
- ${placementConfig.tipPosition} &:before {
+ ${({ placement }) => placementConfig.tipPosition[placement]};
+
+ &:before {
content: '';
position: absolute;
- ${props => placementConfig.arrowPosition[props.placement]};
+ ${({ placement }) => placementConfig.arrowPosition[placement]};
}
`;
+const TipText = styled.span`
+ display: inline-block;
+ max-width: 250px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+`;
+
const Wrapper = styled.div`
position: relative;
- white-space: ${props =>
- props.length >= TIP_MAXLENGTH ? 'initial' : 'nowrap'};
-
- ${Tip} {
- width: ${props => (props.length >= TIP_MAXLENGTH ? '200px' : 'initial')};
- }
`;
class Tooltip extends Component {
constructor(props) {
super(props);
-
- this.state = { show: false, width: null, height: null };
- }
-
- componentDidMount() {
- this.measure();
- }
-
- shouldComponentUpdate(nextProps, nextState) {
- const { text, visible, children } = this.props;
- const { show, width, height } = this.state;
-
- return (
- text !== nextProps.text ||
- show !== nextState.show ||
- visible !== nextProps.visible ||
- width !== nextState.width ||
- height !== nextState.height ||
- children !== nextProps.children
- );
- }
-
- componentDidUpdate() {
- this.measure();
+ const { visible } = this.props;
+ this.state = { visible };
}
- handleEnter = () => {
- this.setState({ show: true });
- };
-
- handleLeave = () => {
- this.setState({ show: false });
- };
-
- measure() {
- const { clientWidth, clientHeight } = this.tip;
-
- this.setState({ width: clientWidth, height: clientHeight });
- }
+ isVisible = visible => this.setState({ visible });
render() {
- const { children, placement, text, visible, ...rest } = this.props;
- const { width, height, show } = this.state;
- const { length } = text;
+ const {
+ children,
+ placement,
+ text,
+ visible: visibleProp,
+ ...rest
+ } = this.props;
+ const { visible: visibleState } = this.state;
return (
this.isVisible(true)}
+ onMouseLeave={() => this.isVisible(false)}
{...rest}
>
- {
- this.tip = tip;
- }}
- placement={placement}
- width={width}
- height={height}
- show={visible || show}
- >
- {text}
+
+ {text}
{children}
@@ -114,7 +78,7 @@ Tip.displayName = 'Tip';
Tooltip.propTypes = {
/** Text that tooltip will show */
- text: PropTypes.string,
+ text: PropTypes.string.isRequired,
/** Define tooltip positioning */
placement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
visible: PropTypes.bool,
@@ -125,9 +89,8 @@ Tooltip.propTypes = {
};
Tooltip.defaultProps = {
- text: 'Tooltip',
placement: 'top',
- visible: undefined,
+ visible: false,
};
export default Tooltip;
diff --git a/components/Tooltip/Tooltip.unit.test.jsx b/components/Tooltip/Tooltip.unit.test.jsx
index 3ef569f83..e74f4c176 100644
--- a/components/Tooltip/Tooltip.unit.test.jsx
+++ b/components/Tooltip/Tooltip.unit.test.jsx
@@ -2,25 +2,43 @@ import React from 'react';
import { mount } from 'enzyme';
import Tooltip from './Tooltip';
+const TOOLTIP_TEXT = 'This is a hint';
+
describe('Tooltip component ', () => {
describe('All positions', () => {
it('Should match the snapshot when place is top', () => {
- const tooltip = mount(
Hover Me);
+ const tooltip = mount(
+
+ Hover Me
+ ,
+ );
expect(tooltip.html()).toMatchSnapshot();
});
it('Should match the snapshot when place is right', () => {
- const tooltip = mount(
Hover Me);
+ const tooltip = mount(
+
+ Hover Me
+ ,
+ );
expect(tooltip.html()).toMatchSnapshot();
});
it('Should match the snapshot when place is bottom', () => {
- const tooltip = mount(
Hover Me);
+ const tooltip = mount(
+
+ Hover Me
+ ,
+ );
expect(tooltip.html()).toMatchSnapshot();
});
it('Should match the snapshot when place is left', () => {
- const tooltip = mount(
Hover Me);
+ const tooltip = mount(
+
+ Hover Me
+ ,
+ );
expect(tooltip.html()).toMatchSnapshot();
});
});
diff --git a/components/Tooltip/__snapshots__/Tooltip.unit.test.jsx.snap b/components/Tooltip/__snapshots__/Tooltip.unit.test.jsx.snap
index 0cec220c5..c61e94a20 100644
--- a/components/Tooltip/__snapshots__/Tooltip.unit.test.jsx.snap
+++ b/components/Tooltip/__snapshots__/Tooltip.unit.test.jsx.snap
@@ -1,9 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Tooltip component All positions Should match the snapshot when place is bottom 1`] = `"
"`;
+exports[`Tooltip component All positions Should match the snapshot when place is bottom 1`] = `"
"`;
-exports[`Tooltip component All positions Should match the snapshot when place is left 1`] = `"
"`;
+exports[`Tooltip component All positions Should match the snapshot when place is left 1`] = `"
"`;
-exports[`Tooltip component All positions Should match the snapshot when place is right 1`] = `"
"`;
+exports[`Tooltip component All positions Should match the snapshot when place is right 1`] = `"
"`;
-exports[`Tooltip component All positions Should match the snapshot when place is top 1`] = `"
"`;
+exports[`Tooltip component All positions Should match the snapshot when place is top 1`] = `"
"`;
diff --git a/components/Tooltip/options.js b/components/Tooltip/options.js
index 0ce2e1700..8351fb21d 100644
--- a/components/Tooltip/options.js
+++ b/components/Tooltip/options.js
@@ -1,63 +1,69 @@
-const arrowSize = 6;
+const ARROW_SIZE = 6;
+const PERCENTAGE_Y = 130;
+const PERCENTAGE_X = 50;
+
const upDownBorders = `
- border-left: ${arrowSize}px solid transparent;
- border-right: ${arrowSize}px solid transparent;
+ border-left: ${ARROW_SIZE}px solid transparent;
+ border-right: ${ARROW_SIZE}px solid transparent;
left: 50%;
transform: translateX(-50%);
`;
const sideBorders = `
- border-top: ${arrowSize}px solid transparent;
- border-bottom: ${arrowSize}px solid transparent;
+ border-top: ${ARROW_SIZE}px solid transparent;
+ border-bottom: ${ARROW_SIZE}px solid transparent;
top: 50%;
transform: translateY(-50%);
`;
const placementConfig = {
arrowPosition: {
- bottom: `
- ${upDownBorders}
- border-bottom: ${arrowSize}px solid;
- border-bottom-color: inherit;
- top: -${arrowSize - 1}px;
- `,
top: `
${upDownBorders}
- border-top: ${arrowSize}px solid;
+ border-top: ${ARROW_SIZE}px solid;
border-top-color: inherit;
- bottom: -${arrowSize - 1}px;
+ bottom: -${ARROW_SIZE - 1}px;
`,
+ right: `
+ ${sideBorders}
+ border-right: ${ARROW_SIZE}px solid;
+ border-right-color: inherit;
+ left: -${ARROW_SIZE - 1}px;
+ `,
left: `
${sideBorders}
- border-left: ${arrowSize}px solid;
+ border-left: ${ARROW_SIZE}px solid;
border-left-color: inherit;
- right: -${arrowSize - 1}px;
+ right: -${ARROW_SIZE - 1}px;
`,
- right: `
- ${sideBorders}
- border-right: ${arrowSize}px solid;
- border-right-color: inherit;
- left: -${arrowSize - 1}px;
+ bottom: `
+ ${upDownBorders}
+ border-bottom: ${ARROW_SIZE}px solid;
+ border-bottom-color: inherit;
+ top: -${ARROW_SIZE - 1}px;
`,
},
-
- tipPosition: ({ placement, height, width }) => {
- const position = {
- top: `top: -${height + 5}px; left: 50%; margin-left: -${Math.floor(
- width / 2,
- )}px;`,
- right: `right: -${width + 15}px;top: 50%; margin-top: -${Math.floor(
- height / 2,
- )}px;`,
- bottom: `bottom: -${height + 10}px; left: 50%; margin-left: -${Math.floor(
- width / 2,
- )}px;`,
- left: `left: -${width + 15}px;top: 50%; margin-top: -${Math.floor(
- height / 2,
- )}px;`,
- };
-
- return position[placement] || position.top;
+ tipPosition: {
+ top: `
+ left: ${PERCENTAGE_X}%;
+ bottom: ${PERCENTAGE_Y}%;
+ transform: translateX(-${PERCENTAGE_X}%);
+ `,
+ right: `
+ left: ${PERCENTAGE_Y}%;
+ top: ${PERCENTAGE_X}%;
+ transform: translateY(-${PERCENTAGE_X}%);
+ `,
+ left: `
+ right: ${PERCENTAGE_Y}%;
+ top: ${PERCENTAGE_X}%;
+ transform: translateY(-${PERCENTAGE_X}%);
+ `,
+ bottom: `
+ left: ${PERCENTAGE_X}%;
+ top: ${PERCENTAGE_Y}%;
+ transform: translateX(-${PERCENTAGE_X}%);
+ `,
},
};
diff --git a/components/index.js b/components/index.js
index 793ec5e2e..1614135d3 100644
--- a/components/index.js
+++ b/components/index.js
@@ -1,6 +1,8 @@
+import Alert from './Alert';
import { BREAKPOINTS } from './shared';
import { Container, Row, Col, Hide } from './Grid';
import { Form, Validations } from './Form';
+import { TabbedView, Tab } from './TabbedView';
import Badge from './Badge';
import Button from './Button';
import Checkbox from './Checkbox';
@@ -16,8 +18,10 @@ import Tag from './Tag';
import TextArea from './TextArea';
import Toggle from './Toggle';
import Tooltip from './Tooltip';
+import Modal from './Modal';
export {
+ Alert,
Badge,
BREAKPOINTS,
Button,
@@ -41,5 +45,8 @@ export {
Toggle,
Tooltip,
Typography,
+ TabbedView,
+ Tab,
Validations,
+ Modal,
};
diff --git a/package.json b/package.json
index 139f42ffc..65fac3f88 100644
--- a/package.json
+++ b/package.json
@@ -73,6 +73,7 @@
"dotenv-webpack": "^1.6.0",
"enzyme": "^3.8.0",
"enzyme-adapter-react-16": "^1.7.1",
+ "enzyme-to-json": "^3.3.5",
"eslint": "^5.15.3",
"eslint-config-airbnb": "^17.1.0",
"eslint-config-prettier": "^3.3.0",
@@ -97,7 +98,7 @@
"styled-components": "^4.1.3"
},
"dependencies": {
- "@catho/quantum-storybook-ui": "^1.0.1",
+ "@catho/quantum-storybook-ui": "^1.0.3",
"@material-ui/core": "^3.8.2",
"@material-ui/icons": "^3.0.2",
"babel-polyfill": "^6.26.0",
diff --git a/stories/Alert/Alert.story.jsx b/stories/Alert/Alert.story.jsx
new file mode 100644
index 000000000..99ec9a3d2
--- /dev/null
+++ b/stories/Alert/Alert.story.jsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import { AutoExample } from '@catho/quantum-storybook-ui';
+import { Alert } from '../../components';
+
+const description = `Alerts are used for items that need to be labeled,
+categorized, or organized using keywords that describe them.`;
+
+const sampleChildren = (
+
+ This is a sample message, a much bigger text could fit in
+ here
+
+ and a buch of other lines too, just to demonstrate
+
+ how this component behaves ❤
+
+);
+
+storiesOf('Alert', module).add('Alert', () => (
+
{},
+ icon: 'info',
+ }}
+ />
+));
diff --git a/stories/Button/Button.story.jsx b/stories/Button/Button.story.jsx
index 2c6c55011..df85b53d8 100644
--- a/stories/Button/Button.story.jsx
+++ b/stories/Button/Button.story.jsx
@@ -115,6 +115,9 @@ storiesOf('Buttons', module).add('Button', () => (
>
diff --git a/stories/Card/Card.story.jsx b/stories/Card/Card.story.jsx
index 8bd4de136..8cc965271 100644
--- a/stories/Card/Card.story.jsx
+++ b/stories/Card/Card.story.jsx
@@ -39,14 +39,14 @@ storiesOf('Card', module).add('Card', () => (
-
+
Component |
Description |
-
+ |
{''}
|
diff --git a/stories/Modal/Modal.story.jsx b/stories/Modal/Modal.story.jsx
new file mode 100644
index 000000000..5c884cd56
--- /dev/null
+++ b/stories/Modal/Modal.story.jsx
@@ -0,0 +1,111 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import {
+ TabbedView,
+ Tab,
+ AutoPropsApi,
+ Heading,
+ StoryContainer,
+ Title,
+ SimpleHighlight,
+ Table,
+} from '@catho/quantum-storybook-ui';
+import { Modal, Row, Col } from '../../components';
+import ModalExample from './examples/ModalExample';
+
+storiesOf('Modal', module).add('Modal', () => (
+ <>
+
+ Modals inform users about a task and can contain critical information,
+ require decisions, or involve multiple tasks.
+
+
+
+
+ Importing Modal
+ {`import { Modal } from '@catho/quantum';`}
+
+ Compound Components
+ We provide a few components to build your Modal.
+
+
+
+
+
+
+ Component |
+ Description |
+
+
+
+
+
+ {''}
+ |
+
+ Modal wrapper, it's the root element of Modal
+ component. Hold all Modal elements.
+ |
+
+
+
+ {''}
+ |
+ It's envolve the Modal header components. |
+
+
+
+ {''}
+ |
+ It's envolve the Modal.Title component. |
+
+
+
+ {''}
+ |
+ The Modal main title. |
+
+
+
+ {''}
+ |
+
+ Footer is provided for include actions buttons, icons, any
+ kind of user interaction.
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Simple Modal
+
+ Here you can check a simple implamentation using Modal component.
+
+
+
+
+ {ModalExample.code}
+
+
+
+
+
+
+
+
+ >
+));
diff --git a/stories/Modal/examples/ModalExample.jsx b/stories/Modal/examples/ModalExample.jsx
new file mode 100644
index 000000000..ac2d84d10
--- /dev/null
+++ b/stories/Modal/examples/ModalExample.jsx
@@ -0,0 +1,120 @@
+import React, { Component } from 'react';
+import { Button, Modal } from '../../../components';
+
+class ModalExample extends Component {
+ constructor() {
+ super();
+
+ this.state = {
+ showModal: false,
+ };
+ }
+
+ openModal = () => this.setState({ showModal: true });
+
+ closeModal = () => this.setState({ showModal: false });
+
+ render() {
+ const { showModal } = this.state;
+
+ return (
+ <>
+
+ {showModal && (
+
+
+
+
+ Are you sure you want to delete this item?
+
+
+
+
+ You will not be able to recover this item later.
+
+
+
+
+
+
+
+
+ )}
+ >
+ );
+ }
+}
+
+ModalExample.code = `import React, { Component } from 'react';
+import { Button, Modal } from '@catho/quantum';
+
+class ModalExample extends Component {
+ constructor() {
+ super();
+
+ this.state = {
+ showModal: false,
+ };
+ }
+
+ openModal = () => this.setState({ showModal: true });
+
+ closeModal = () => this.setState({ showModal: false });
+
+ render() {
+ const { showModal } = this.state;
+
+ return (
+ <>
+
+ {showModal && (
+
+
+
+
+ Are you sure you want to delete this item?
+
+
+
+
+ You will not be able to recover this item later.
+
+
+
+
+
+
+
+
+ )}
+ >
+ );
+ }
+}
+
+export default ModalExample;
+`;
+
+export default ModalExample;
diff --git a/stories/Slider/Slider.story.jsx b/stories/Slider/Slider.story.jsx
index a319563f0..e4d0f3b9a 100644
--- a/stories/Slider/Slider.story.jsx
+++ b/stories/Slider/Slider.story.jsx
@@ -130,7 +130,7 @@ storiesOf('Forms', module).add('Slider', () => (
.
- {/*
+
Controlled Slider
it's needed an{' '}
@@ -155,7 +155,7 @@ storiesOf('Forms', module).add('Slider', () => (
.
- */}
+
diff --git a/stories/TabbedView/TabbedView.story.jsx b/stories/TabbedView/TabbedView.story.jsx
new file mode 100644
index 000000000..79c164aca
--- /dev/null
+++ b/stories/TabbedView/TabbedView.story.jsx
@@ -0,0 +1,95 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import {
+ Heading,
+ TabbedView,
+ Tab,
+ StoryContainer,
+ AutoPropsApi,
+ SimpleHighlight,
+} from '@catho/quantum-storybook-ui';
+import {
+ Row,
+ Col,
+ Container,
+ TabbedView as QuantumTabbedView,
+} from '../../components';
+
+storiesOf('TabbedView', module).add('TabbedView', () => (
+ <>
+
+ TabbedView and Tab are components created to organize content into
+ separate views where only one view can be visible at a time.
+
+
+
+
+
+
+ Think in TabbedView as a RadioGroup proper to group big context such
+ as menus, dynamic forms, navigation, and so on.
+
+
+ To do this, we provide TabbedView ,{' '}
+ TabbedView.Tab , and Tab .
+
+
+
+
+ {`import { TabbedView, Tab } from '@catho/quantum';
+
+ // optional
+
+ Candidates content
+
+
+ Companies content
+
+
+ Education content
+
+`}
+
+
+
+
+
+ Candidates content
+
+
+ Companies content
+
+
+ Education content
+
+
+
+
+
+
+ Candidates content
+
+
+ Companies content
+
+
+ Education content
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+));
diff --git a/stories/Tooltip/Tooltip.story.jsx b/stories/Tooltip/Tooltip.story.jsx
index a28d25141..c8960afa1 100644
--- a/stories/Tooltip/Tooltip.story.jsx
+++ b/stories/Tooltip/Tooltip.story.jsx
@@ -12,6 +12,7 @@ storiesOf('Tooltip', module).add('Basic', () => (
component={Tooltip}
componentProps={{
children: 'Hover me',
+ text: 'This is a hint.',
}}
/>
));
diff --git a/yarn.lock b/yarn.lock
index 8acae6ad1..d114d7f74 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -857,18 +857,28 @@
lodash "^4.17.11"
to-fast-properties "^2.0.0"
-"@catho/quantum-storybook-ui@^1.0.0", "@catho/quantum-storybook-ui@^1.0.1":
+"@catho/quantum-storybook-ui@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@catho/quantum-storybook-ui/-/quantum-storybook-ui-1.0.1.tgz#aca8f121cad96d1a856ef5eb80af8f198025f73d"
dependencies:
"@catho/quantum" "^1.2.0"
react-markdown "^4.0.6"
-"@catho/quantum@^1.2.0":
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/@catho/quantum/-/quantum-1.2.0.tgz#ea2fdbdf2c117bce11665a75c6ee38afb5d93faa"
+"@catho/quantum-storybook-ui@^1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@catho/quantum-storybook-ui/-/quantum-storybook-ui-1.0.3.tgz#6698b1810ad67a11de0679cc1f2728c3d624752b"
+ integrity sha512-t+Ac7U+fPaO3mZny9KzTugKxaMTqUUgeSoYIqABa9YaTOHbTocrgZ6+iALfTFjpWkysYOjKkzoAA9RpDm6TOUA==
dependencies:
- "@catho/quantum-storybook-ui" "^1.0.0"
+ "@catho/quantum" "^1.5.1"
+ commitizen "^3.0.7"
+ react-markdown "^4.0.6"
+
+"@catho/quantum@^1.5.1":
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/@catho/quantum/-/quantum-1.7.0.tgz#c8fa6b984a685ef274b6dfe4ca14c266c787485e"
+ integrity sha512-T9NsMxG2GZTMKqV5RKBfIcptHgAK5BsS0kPcxqWGGES0on4njCwZ9XNF7NjUuP03vGuv4vWniOmNTMwMLpI/sw==
+ dependencies:
+ "@catho/quantum-storybook-ui" "^1.0.1"
"@material-ui/core" "^3.8.2"
"@material-ui/icons" "^3.0.2"
babel-polyfill "^6.26.0"
@@ -4356,6 +4366,13 @@ enzyme-adapter-utils@^1.10.1:
prop-types "^15.7.2"
semver "^5.6.0"
+enzyme-to-json@^3.3.5:
+ version "3.3.5"
+ resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.3.5.tgz#f8eb82bd3d5941c9d8bc6fd9140030777d17d0af"
+ integrity sha512-DmH1wJ68HyPqKSYXdQqB33ZotwfUhwQZW3IGXaNXgR69Iodaoj8TF/D9RjLdz4pEhGq2Tx2zwNUIjBuqoZeTgA==
+ dependencies:
+ lodash "^4.17.4"
+
enzyme@^3.8.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.9.0.tgz#2b491f06ca966eb56b6510068c7894a7e0be3909"
|