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; +} + +
+
+ + Sample alert + + +
+
+`; + +exports[`Alert component When you set a alert custom icon Should match the snapshot with an icon 1`] = ` +.c8 { + pointer-events: none; +} + +.c6 { + 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; +} + +.c6 *:nth-child(2) { + margin-left: 5px; +} + +.c6 .c7 { + font-size: 24px; +} + +.c6:hover { + box-shadow: 0 2px 4px 0 #cccccc; + background-color: #002f7b; + border-color: #002f7b; + color: #FFFFFF; +} + +.c6:focus { + box-shadow: 0 2px 6px 0 rgba(19,85,208,0.5); + background-color: #1355d0; + border-color: #1355d0; + color: #FFFFFF; +} + +.c6: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; +} + +.c5 { + 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; +} + +.c5:hover, +.c5:focus { + box-shadow: none; + background-color: rgba(204,204,204,0.4); + color: #4c4c4c; +} + +.c5:active { + box-shadow: none; + background-color: rgba(128,128,128,0.5); + color: #4c4c4c; +} + +.c5: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 .c3 { + color: #1355d0; + margin-right: 16px; +} + +.c0 .c1 .c4 { + color: #1355d0; +} + +
+
+ + + Sample alert + + +
+
+`; + +exports[`Alert component When you set a different skin Should match a skin snapshot 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; +} + +
+
+ + blue + + +
+
+`; + +exports[`Alert component When you set a different skin Should match a skin snapshot 2`] = ` +.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; +} + +.c8 .c1 .c9 { + color: #1355d0; + margin-right: 16px; +} + +.c8 .c1 .c3 { + color: #1355d0; +} + +.c0 { + border-radius: 8px; + box-sizing: border-box; + padding: 11px 16px; + color: #3c6510; + background-color: #edfadf; + border: 1.5px solid #7ed321; +} + +.c0 .c1 .c9 { + color: #7ed321; + margin-right: 16px; +} + +.c0 .c1 .c3 { + color: #7ed321; +} + +
+
+ + success + + +
+
+`; + +exports[`Alert component When you set a different skin Should match a skin snapshot 3`] = ` +.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; +} + +.c8 .c1 .c10 { + color: #1355d0; + margin-right: 16px; +} + +.c8 .c1 .c3 { + color: #1355d0; +} + +.c9 .c1 .c10 { + color: #7ed321; + margin-right: 16px; +} + +.c9 .c1 .c3 { + color: #7ed321; +} + +.c0 { + border-radius: 8px; + box-sizing: border-box; + padding: 11px 16px; + color: #a36300; + background-color: #ffefd6; + border: 1.5px solid #f09100; +} + +.c0 .c1 .c10 { + color: #f09100; + margin-right: 16px; +} + +.c0 .c1 .c3 { + color: #f09100; +} + +
+
+ + warning + + +
+
+`; + +exports[`Alert component When you set a different skin Should match a skin snapshot 4`] = ` +.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; +} + +.c8 .c1 .c11 { + color: #1355d0; + margin-right: 16px; +} + +.c8 .c1 .c3 { + color: #1355d0; +} + +.c9 .c1 .c11 { + color: #7ed321; + margin-right: 16px; +} + +.c9 .c1 .c3 { + color: #7ed321; +} + +.c10 .c1 .c11 { + color: #f09100; + margin-right: 16px; +} + +.c10 .c1 .c3 { + color: #f09100; +} + +.c0 { + border-radius: 8px; + box-sizing: border-box; + padding: 11px 16px; + color: #ac001a; + background-color: #ffe5e9; + border: 1.5px solid #ff2d4d; +} + +.c0 .c1 .c11 { + color: #ff2d4d; + margin-right: 16px; +} + +.c0 .c1 .c3 { + color: #ff2d4d; +} + +
+
+ + error + + +
+
+`; diff --git a/components/Alert/index.js b/components/Alert/index.js new file mode 100644 index 000000000..961123117 --- /dev/null +++ b/components/Alert/index.js @@ -0,0 +1,3 @@ +import Alert from './Alert'; + +export default Alert; diff --git a/components/Button/Button.jsx b/components/Button/Button.jsx index 23168d810..6c4e7d707 100644 --- a/components/Button/Button.jsx +++ b/components/Button/Button.jsx @@ -55,7 +55,6 @@ const height = ({ size }) => { }; const ButtonIcon = styled(Icon)` - margin-right: 5px; pointer-events: none; `; @@ -181,11 +180,13 @@ Button.propTypes = { const IconButton = styled(Button)` border-radius: 50%; border: none; - color: ${Colors.BLACK[400]}; + color: ${Colors.SHADOW[50]}; width: 40px; + background-color: transparent; box-shadow: none; outline: none; + :hover, :focus { box-shadow: none; diff --git a/components/Button/__snapshots__/Button.unit.test.jsx.snap b/components/Button/__snapshots__/Button.unit.test.jsx.snap index d6d9f2eeb..4b9c80ad0 100644 --- a/components/Button/__snapshots__/Button.unit.test.jsx.snap +++ b/components/Button/__snapshots__/Button.unit.test.jsx.snap @@ -775,7 +775,6 @@ exports[`Button component when there is a type set should match secondary snapsh exports[`Button component with an icon should match secondary snapshot 1`] = ` .c2 { - margin-right: 5px; pointer-events: none; } @@ -888,7 +887,6 @@ exports[`Button component with an icon should match secondary snapshot 1`] = ` exports[`Button component with an icon should match secondary snapshot 2`] = ` .c2 { - margin-right: 5px; pointer-events: none; } @@ -1001,7 +999,6 @@ exports[`Button component with an icon should match secondary snapshot 2`] = ` exports[`Button component with an icon should match secondary snapshot 3`] = ` .c2 { - margin-right: 5px; pointer-events: none; } @@ -1114,7 +1111,6 @@ exports[`Button component with an icon should match secondary snapshot 3`] = ` exports[`Button component with an icon should match secondary snapshot 4`] = ` .c2 { - margin-right: 5px; pointer-events: none; } diff --git a/components/Card/__snapshots__/Card.unit.test.jsx.snap b/components/Card/__snapshots__/Card.unit.test.jsx.snap index 7de0b6808..98c557cf3 100644 --- a/components/Card/__snapshots__/Card.unit.test.jsx.snap +++ b/components/Card/__snapshots__/Card.unit.test.jsx.snap @@ -13,6 +13,10 @@ exports[` Snapshots Should match the snapshot 1`] = ` margin-right: 18px; } +.c1 > *:last-child { + margin-right: 0; +} + .c2 { -webkit-box-flex: 1; -webkit-flex-grow: 1; diff --git a/components/Card/sub-components/Header.jsx b/components/Card/sub-components/Header.jsx index 914ede287..0c664b498 100644 --- a/components/Card/sub-components/Header.jsx +++ b/components/Card/sub-components/Header.jsx @@ -7,6 +7,10 @@ const Header = styled.header` > * { margin-right: 18px; } + + > *:last-child { + margin-right: 0; + } `; Header.displayName = 'Card.Header'; diff --git a/components/Checkbox/Checkbox.jsx b/components/Checkbox/Checkbox.jsx index 51ee474cc..ebfba3109 100644 --- a/components/Checkbox/Checkbox.jsx +++ b/components/Checkbox/Checkbox.jsx @@ -124,7 +124,7 @@ Checkbox.defaultProps = { disabled: false, error: '', id: '', - label: 'Label', + label: '', }; Checkbox.propTypes = { diff --git a/components/Checkbox/__snapshots__/Checkbox.unit.test.jsx.snap b/components/Checkbox/__snapshots__/Checkbox.unit.test.jsx.snap index 07e79ffba..9733e07f6 100644 --- a/components/Checkbox/__snapshots__/Checkbox.unit.test.jsx.snap +++ b/components/Checkbox/__snapshots__/Checkbox.unit.test.jsx.snap @@ -112,7 +112,7 @@ exports[` should match the snapshot 1`] = ` className="c3 c4" htmlFor="" > - Label + @@ -307,7 +307,7 @@ exports[` should match the snapshot 2`] = ` className="c3 c4" htmlFor="" > - Label +
should match the snapshot 3`] = ` className="c3 c4" htmlFor="" > - Label +
should match the snapshot 4`] = ` className="c3 c4" htmlFor="" > - Label +
@@ -886,7 +886,7 @@ exports[` should match the snapshot 5`] = ` className="c3 c4" htmlFor="" > - Label + @@ -1261,7 +1261,7 @@ exports[` should match the snapshot 7`] = ` className="c3 c4" htmlFor="" > - Label + diff --git a/components/Input/Input.jsx b/components/Input/Input.jsx index 66c61d1c8..ac62bb586 100644 --- a/components/Input/Input.jsx +++ b/components/Input/Input.jsx @@ -54,13 +54,7 @@ const InputIcon = styled(Icon)` cursor: pointer; position: absolute; right: 12px; - top: 50px; - - ${({ description }) => - description && - ` - top: 70px; - `}; + bottom: 10px; `; const InputSearchIcon = styled(InputIcon)` diff --git a/components/Input/__snapshots__/Input.unit.test.jsx.snap b/components/Input/__snapshots__/Input.unit.test.jsx.snap index cc200a7bd..8fb14fbc2 100644 --- a/components/Input/__snapshots__/Input.unit.test.jsx.snap +++ b/components/Input/__snapshots__/Input.unit.test.jsx.snap @@ -131,7 +131,7 @@ exports[`Input component should match snapshots 2`] = ` cursor: pointer; position: absolute; right: 12px; - top: 50px; + bottom: 10px; }
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 title +

+
+
+
+ Modal Content +
+
+ ModalFooter +
+ +
+
, + } + } + onClick={[Function]} + role="dialog" + > +
+ + *{margin-right:18px;}> *:last-child{margin-right:0;}", + ], + }, + "displayName": "Card.Header", + "foldedComponentIds": Array [], + "render": [Function], + "styledComponentId": "c3", + "target": "header", + "toString": [Function], + "warnTooManyClasses": [Function], + "withComponent": [Function], + }, + "HeaderText": Object { + "$$typeof": Symbol(react.forward_ref), + "attrs": Array [], + "componentStyle": ComponentStyle { + "componentId": "HeaderText-sc-15zd9gn-0", + "isStatic": true, + "lastClassName": "c5", + "rules": Array [ + "flex-grow:1;", + ], + }, + "displayName": "Card.HeaderText", + "foldedComponentIds": Array [], + "render": [Function], + "styledComponentId": "HeaderText-sc-15zd9gn-0", + "target": "div", + "toString": [Function], + "warnTooManyClasses": [Function], + "withComponent": [Function], + }, + "Media": [Function], + "Thumbnail": [Function], + "Title": [Function], + "attrs": Array [], + "componentStyle": ComponentStyle { + "componentId": "Modal__ModalCard-sc-300qpr-0", + "isStatic": false, + "lastClassName": "c1", + "rules": Array [ + ".c3", + "{padding-right:56px;}", + [Function], + ], + }, + "displayName": "Modal__ModalCard", + "foldedComponentIds": Array [], + "render": [Function], + "styledComponentId": "Modal__ModalCard-sc-300qpr-0", + "target": [Function], + "toString": [Function], + "warnTooManyClasses": [Function], + "withComponent": [Function], + Symbol(Symbol.hasInstance): [Function], + } + } + forwardedRef={null} + > + + + +
+
+ *{margin-right:18px;}> *:last-child{margin-right:0;}", + "padding:24px 24px 0;", + ], + }, + "displayName": "Header", + "foldedComponentIds": Array [ + "c3", + ], + "render": [Function], + "styledComponentId": "Header-sc-13ddtbk-0", + "target": "header", + "toString": [Function], + "warnTooManyClasses": [Function], + "withComponent": [Function], + } + } + forwardedRef={null} + > +
+ + +
+ + <StyledComponent + forwardedComponent={ + Object { + "$$typeof": Symbol(react.forward_ref), + "attrs": Array [], + "componentStyle": ComponentStyle { + "componentId": "Title-hr7zki-0", + "isStatic": true, + "lastClassName": "c6", + "rules": Array [ + "font-weight:700;line-height:1.25;", + ], + }, + "displayName": "Title", + "foldedComponentIds": Array [], + "render": [Function], + "styledComponentId": "Title-hr7zki-0", + "target": [Function], + "toString": [Function], + "warnTooManyClasses": [Function], + "withComponent": [Function], + Symbol(Symbol.hasInstance): [Function], + } + } + forwardedRef={null} + > + <Card.Title + className="c6" + small={false} + > + <Title__Heading + className="c6" + small={false} + > + <StyledComponent + className="c6" + forwardedComponent={ + Object { + "$$typeof": Symbol(react.forward_ref), + "attrs": Array [], + "componentStyle": ComponentStyle { + "componentId": "Title__Heading-sc-14fvmc0-0", + "isStatic": false, + "lastClassName": "c7", + "rules": Array [ + "font-size:", + [Function], + ";font-weight:600;margin:0;", + ], + }, + "displayName": "Title__Heading", + "foldedComponentIds": Array [], + "render": [Function], + "styledComponentId": "Title__Heading-sc-14fvmc0-0", + "target": "h2", + "toString": [Function], + "warnTooManyClasses": [Function], + "withComponent": [Function], + } + } + forwardedRef={null} + small={false} + > + <h2 + className="c6 c7" + > + Modal title + </h2> + </StyledComponent> + </Title__Heading> + </Card.Title> + </StyledComponent> + +
+
+
+
+
+
+ + +
+ Modal Content +
+
+
+ + +
+ ModalFooter +
+
+
+ + + + + + + + +
+
+
+
+
+
+
+ + + + +`; diff --git a/components/Modal/index.js b/components/Modal/index.js new file mode 100644 index 000000000..8144af51b --- /dev/null +++ b/components/Modal/index.js @@ -0,0 +1,3 @@ +import Modal from './Modal'; + +export default Modal; diff --git a/components/Modal/sub-components/Content.jsx b/components/Modal/sub-components/Content.jsx new file mode 100644 index 000000000..54e580ce1 --- /dev/null +++ b/components/Modal/sub-components/Content.jsx @@ -0,0 +1,11 @@ +import styled from 'styled-components'; +import Card from '../../Card'; + +const Content = styled(Card.Content)` + font-size: 16px; + max-height: 70vh; + overflow-y: auto; + padding: 16px 24px; +`; + +export default Content; diff --git a/components/Modal/sub-components/Footer.jsx b/components/Modal/sub-components/Footer.jsx new file mode 100644 index 000000000..d6db24551 --- /dev/null +++ b/components/Modal/sub-components/Footer.jsx @@ -0,0 +1,3 @@ +import Card from '../../Card'; + +export default Card.Footer; diff --git a/components/Modal/sub-components/Header.jsx b/components/Modal/sub-components/Header.jsx new file mode 100644 index 000000000..f9bd28684 --- /dev/null +++ b/components/Modal/sub-components/Header.jsx @@ -0,0 +1,8 @@ +import styled from 'styled-components'; +import Card from '../../Card'; + +const Header = styled(Card.Header)` + padding: 24px 24px 0; +`; + +export default Header; diff --git a/components/Modal/sub-components/HeaderText.jsx b/components/Modal/sub-components/HeaderText.jsx new file mode 100644 index 000000000..9e7484fac --- /dev/null +++ b/components/Modal/sub-components/HeaderText.jsx @@ -0,0 +1,3 @@ +import Card from '../../Card'; + +export default Card.HeaderText; diff --git a/components/Modal/sub-components/Title.jsx b/components/Modal/sub-components/Title.jsx new file mode 100644 index 000000000..11edd22da --- /dev/null +++ b/components/Modal/sub-components/Title.jsx @@ -0,0 +1,9 @@ +import styled from 'styled-components'; +import Card from '../../Card'; + +const Title = styled(Card.Title)` + font-weight: 700; + line-height: 1.25; +`; + +export default Title; diff --git a/components/Modal/sub-components/index.js b/components/Modal/sub-components/index.js new file mode 100644 index 000000000..d4531b03b --- /dev/null +++ b/components/Modal/sub-components/index.js @@ -0,0 +1,7 @@ +import Content from './Content'; +import Header from './Header'; +import HeaderText from './HeaderText'; +import Title from './Title'; +import Footer from './Footer'; + +export { Content, Header, HeaderText, Title, Footer }; diff --git a/components/Slider/__snapshots__/Slider.unit.test.jsx.snap b/components/Slider/__snapshots__/Slider.unit.test.jsx.snap index 990e8109f..19761c38d 100644 --- a/components/Slider/__snapshots__/Slider.unit.test.jsx.snap +++ b/components/Slider/__snapshots__/Slider.unit.test.jsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` Snapshots should match the snapshot when is disabled 1`] = ` -.c3 { +.c2 { background-color: #4c4c4c; border-color: #4c4c4c; border-radius: 4px; @@ -11,17 +11,19 @@ exports[` Snapshots should match the snapshot when is disab opacity: 0; padding: 4px 8px; position: absolute; + line-height: 0; text-align: center; -webkit-transition: opacity 0.2s ease-in-out,visibility 0.2s ease-in-out; transition: opacity 0.2s ease-in-out,visibility 0.2s ease-in-out; - visibility: hidden; z-index: 100; - top: -1085px; left: 50%; - margin-left: -960px; + bottom: 130%; + -webkit-transform: translateX(-50%); + -ms-transform: translateX(-50%); + transform: translateX(-50%); } -.c3:before { +.c2:before { content: ''; position: absolute; border-left: 6px solid transparent; @@ -35,13 +37,16 @@ exports[` Snapshots should match the snapshot when is disab bottom: -5px; } -.c1 { - position: relative; +.c3 { + display: inline-block; + max-width: 250px; + overflow: hidden; + text-overflow: ellipsis; white-space: nowrap; } -.c1 .c2 { - width: initial; +.c1 { + position: relative; } .c4 .rc-slider-handle { @@ -98,11 +103,13 @@ exports[` Snapshots should match the snapshot when is disab value={50} >
- 50 + + 50 +
Snapshots should match the snapshot when is disab `; exports[` Snapshots should match the snapshot when have just one handle 1`] = ` -.c3 { +.c2 { background-color: #4c4c4c; border-color: #4c4c4c; border-radius: 4px; @@ -224,17 +231,19 @@ exports[` Snapshots should match the snapshot when have just one handl opacity: 0; padding: 4px 8px; position: absolute; + line-height: 0; text-align: center; -webkit-transition: opacity 0.2s ease-in-out,visibility 0.2s ease-in-out; transition: opacity 0.2s ease-in-out,visibility 0.2s ease-in-out; - visibility: hidden; z-index: 100; - top: -1085px; left: 50%; - margin-left: -960px; + bottom: 130%; + -webkit-transform: translateX(-50%); + -ms-transform: translateX(-50%); + transform: translateX(-50%); } -.c3:before { +.c2:before { content: ''; position: absolute; border-left: 6px solid transparent; @@ -248,13 +257,16 @@ exports[` Snapshots should match the snapshot when have just one handl bottom: -5px; } -.c1 { - position: relative; +.c3 { + display: inline-block; + max-width: 250px; + overflow: hidden; + text-overflow: ellipsis; white-space: nowrap; } -.c1 .c2 { - width: initial; +.c1 { + position: relative; } .c4 .rc-slider-handle { @@ -311,11 +323,13 @@ exports[` Snapshots should match the snapshot when have just one handl value={10} >
- 10 + + 10 +
Snapshots should match the snapshot when have just one handl `; exports[` Snapshots should match the snapshot when have two handles 1`] = ` -.c3 { +.c2 { background-color: #4c4c4c; border-color: #4c4c4c; border-radius: 4px; @@ -437,17 +451,19 @@ exports[` Snapshots should match the snapshot when have two handles 1` opacity: 0; padding: 4px 8px; position: absolute; + line-height: 0; text-align: center; -webkit-transition: opacity 0.2s ease-in-out,visibility 0.2s ease-in-out; transition: opacity 0.2s ease-in-out,visibility 0.2s ease-in-out; - visibility: hidden; z-index: 100; - top: -1085px; left: 50%; - margin-left: -960px; + bottom: 130%; + -webkit-transform: translateX(-50%); + -ms-transform: translateX(-50%); + transform: translateX(-50%); } -.c3:before { +.c2:before { content: ''; position: absolute; border-left: 6px solid transparent; @@ -461,13 +477,16 @@ exports[` Snapshots should match the snapshot when have two handles 1` bottom: -5px; } -.c1 { - position: relative; +.c3 { + display: inline-block; + max-width: 250px; + overflow: hidden; + text-overflow: ellipsis; white-space: nowrap; } -.c1 .c2 { - width: initial; +.c1 { + position: relative; } .c4 .rc-slider-handle { @@ -529,11 +548,13 @@ exports[` Snapshots should match the snapshot when have two handles 1` } >
- 10 to 30 + + 10 to 30 +
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 [ + +
+

+ Example text +

+
+
, + ], + ], + }, + "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":
+

+ Example text +

+
, + "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 [ + +
+

+ Example text +

+
+
, + ], + ], + }, + "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":
+

+ Example text +

+
, + "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`] = `"
Tooltip
Hover Me
"`; +exports[`Tooltip component All positions Should match the snapshot when place is bottom 1`] = `"
This is a hint
Hover Me
"`; -exports[`Tooltip component All positions Should match the snapshot when place is left 1`] = `"
Tooltip
Hover Me
"`; +exports[`Tooltip component All positions Should match the snapshot when place is left 1`] = `"
This is a hint
Hover Me
"`; -exports[`Tooltip component All positions Should match the snapshot when place is right 1`] = `"
Tooltip
Hover Me
"`; +exports[`Tooltip component All positions Should match the snapshot when place is right 1`] = `"
This is a hint
Hover Me
"`; -exports[`Tooltip component All positions Should match the snapshot when place is top 1`] = `"
Tooltip
Hover Me
"`; +exports[`Tooltip component All positions Should match the snapshot when place is top 1`] = `"
This is a hint
Hover Me
"`; 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.

+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ComponentDescription
+ {''} + + 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"