diff --git a/ui_framework/dist/ui_framework.css b/ui_framework/dist/ui_framework.css index c54d1b14c3bf46..a51004bc0cff5c 100644 --- a/ui_framework/dist/ui_framework.css +++ b/ui_framework/dist/ui_framework.css @@ -903,7 +903,7 @@ main { transition: opacity 250ms cubic-bezier(0.34, 1.61, 0.7, 1), -webkit-transform 250ms cubic-bezier(0.34, 1.61, 0.7, 1); transition: transform 250ms cubic-bezier(0.34, 1.61, 0.7, 1), opacity 250ms cubic-bezier(0.34, 1.61, 0.7, 1); transition: transform 250ms cubic-bezier(0.34, 1.61, 0.7, 1), opacity 250ms cubic-bezier(0.34, 1.61, 0.7, 1), -webkit-transform 250ms cubic-bezier(0.34, 1.61, 0.7, 1); } - .kuiExpressionItem__popover.ng-hide, .kuiExpressionItem__popover.kuiExpressionItem__popover--isHidden { + .kuiExpressionItem__popover.ng-hide { display: block !important; visibility: hidden; opacity: 0; diff --git a/ui_framework/doc_site/src/services/routes/routes.js b/ui_framework/doc_site/src/services/routes/routes.js index 11be3d404d1358..8d22b0225a0573 100644 --- a/ui_framework/doc_site/src/services/routes/routes.js +++ b/ui_framework/doc_site/src/services/routes/routes.js @@ -163,6 +163,7 @@ const components = [{ }, { name: 'Expression', component: ExpressionExample, + hasReact: true, }, { name: 'Form', component: FormExample, diff --git a/ui_framework/doc_site/src/views/expression/expression.html b/ui_framework/doc_site/src/views/expression/expression.html deleted file mode 100644 index 32325d687c6389..00000000000000 --- a/ui_framework/doc_site/src/views/expression/expression.html +++ /dev/null @@ -1,59 +0,0 @@ -
- -
-
- When -
-
- -
-
-
-
- -
-
- Is above -
-
- - - - - -
-
-
diff --git a/ui_framework/doc_site/src/views/expression/expression.js b/ui_framework/doc_site/src/views/expression/expression.js index 9b63eb4c6bdcdc..e917dba12851bd 100644 --- a/ui_framework/doc_site/src/views/expression/expression.js +++ b/ui_framework/doc_site/src/views/expression/expression.js @@ -1,26 +1,145 @@ -/* eslint-disable */ +import React, { PropTypes } from 'react'; +import { + KuiExpressionItem, + KuiExpressionItemButton, + KuiExpressionItemPopover, +} from '../../../../components'; -let isPopoverCollapsed = true; -const $toggleExpressionButton = $('[data-id="toggleExpressionButton"]'); -const $toggleExpressionPopover = $('[data-id="toggleExpressionPopover"]'); -$('[data-id="toggleExpressionButton"]').click(function() { +class KuiExpressionItemExample extends React.Component { + constructor(props) { + super(props); - if (isPopoverCollapsed) { - $(this).addClass('kuiExpressionItem__button--isActive') - $(this).parent( "div" ).find('[data-id="toggleExpressionPopover"]').removeClass('kuiExpressionItem__popover--isHidden'); - } else { - $(this).removeClass('kuiExpressionItem__button--isActive') - $(this).parent( "div" ).find('[data-id="toggleExpressionPopover"]').addClass('kuiExpression__popover--isHidden'); + this.state = { + example1: { + value: 'count()' + }, + example2: { + object: 'A', + value: '100', + description: 'Is above' + }, + activeButton: props.defaultActiveButton + }; } - isPopoverCollapsed = !isPopoverCollapsed; -}); + changeExample1 = (event) => { + this.setState({ example1: { ...this.state.example1, value: event.target.value } }); + } + + changeExample2Object = (event) => { + this.setState({ example2: { ...this.state.example2, object: event.target.value } }); + } + + changeExample2Value = (event) => { + this.setState({ example2: { ...this.state.example2, value: event.target.value } }); + } -$(document).mouseup(function(e) { - if (!isPopoverCollapsed && !$toggleExpressionPopover.is(e.target) && $toggleExpressionPopover.has(e.target).length === 0) { - $toggleExpressionPopover.addClass('kuiExpressionItem__popover--isHidden'); - $toggleExpressionButton.removeClass('kuiExpressionItem__button--isActive'); - isPopoverCollapsed = !isPopoverCollapsed; + changeExample2Description = (event) => { + this.setState({ example2: { ...this.state.example2, description: event.target.value } }); } -}); + + onOutsideClick = () => { + this.setState({ activeButton:null }); + } + + render() { + //Rise the popovers above GuidePageSideNav + const popoverStyle = { zIndex:'200' }; + + const popover1 = (this.state.activeButton === 'example1') ? this.getPopover1(popoverStyle) : null; + const popover2 = (this.state.activeButton === 'example2') ? this.getPopover2(popoverStyle) : null; + + return ( +
+ + this.setState({ activeButton:'example1' })} + /> + {popover1} + + + this.setState({ activeButton:'example2' })} + /> + {popover2} + +
+ ); + } + + getPopover1(popoverStyle) { + return ( + + + + ); + } + + getPopover2(popoverStyle) { + return ( + +
+ + + + + +
+
+ ); + } +} + +KuiExpressionItemExample.propTypes = { + defaultActiveButton: PropTypes.string.isRequired +}; + +export default KuiExpressionItemExample; diff --git a/ui_framework/doc_site/src/views/expression/expression_example.js b/ui_framework/doc_site/src/views/expression/expression_example.js index 9db7dae0a39667..65e0cd8634c9e6 100644 --- a/ui_framework/doc_site/src/views/expression/expression_example.js +++ b/ui_framework/doc_site/src/views/expression/expression_example.js @@ -1,4 +1,5 @@ import React from 'react'; +import { renderToHtml } from '../../services'; import { GuideDemo, @@ -8,19 +9,20 @@ import { GuideText, } from '../../components'; -const expressionHtml = require('./expression.html'); -const expressionJs = require('raw!./expression.js'); +const Expression = require('./expression'); +const expressionSource = require('!!raw!./expression'); +const expressionHtml = renderToHtml(Expression, { defaultActiveButton: 'example2' }); export default props => ( @@ -28,10 +30,9 @@ export default props => ( Left aligned to the button by default. Can be optionally right aligned (as in the last example). - + + + diff --git a/ui_framework/src/components/expression/__snapshots__/expression_item.test.js.snap b/ui_framework/src/components/expression/__snapshots__/expression_item.test.js.snap new file mode 100644 index 00000000000000..d149874c1ecb4a --- /dev/null +++ b/ui_framework/src/components/expression/__snapshots__/expression_item.test.js.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KuiExpressionItem Props children is rendered 1`] = ` +
+ some expression +
+`; + +exports[`KuiExpressionItem renders 1`] = ` +
+`; diff --git a/ui_framework/src/components/expression/__snapshots__/expression_item_button.test.js.snap b/ui_framework/src/components/expression/__snapshots__/expression_item_button.test.js.snap new file mode 100644 index 00000000000000..a3a5581338e273 --- /dev/null +++ b/ui_framework/src/components/expression/__snapshots__/expression_item_button.test.js.snap @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KuiExpressionItemButton Props isActive false renders inactive 1`] = ` + +`; + +exports[`KuiExpressionItemButton Props isActive true renders active 1`] = ` + +`; + +exports[`KuiExpressionItemButton renders 1`] = ` + +`; diff --git a/ui_framework/src/components/expression/__snapshots__/expression_item_popover.test.js.snap b/ui_framework/src/components/expression/__snapshots__/expression_item_popover.test.js.snap new file mode 100644 index 00000000000000..2ea66ad87044b4 --- /dev/null +++ b/ui_framework/src/components/expression/__snapshots__/expression_item_popover.test.js.snap @@ -0,0 +1,80 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KuiExpressionItemPopover Props align renders default 1`] = ` +
+
+ title +
+
+
+`; + +exports[`KuiExpressionItemPopover Props align renders the left class 1`] = ` +
+
+ title +
+
+
+`; + +exports[`KuiExpressionItemPopover Props align renders the right class 1`] = ` +
+
+ title +
+
+
+`; + +exports[`KuiExpressionItemPopover Props children is rendered 1`] = ` +
+
+ title +
+
+ popover content +
+
+`; + +exports[`KuiExpressionItemPopover renders 1`] = ` +
+
+ title +
+
+
+`; diff --git a/ui_framework/src/components/expression/_expression.scss b/ui_framework/src/components/expression/_expression.scss index e8ed6bbcdde31a..e071f7809b882f 100644 --- a/ui_framework/src/components/expression/_expression.scss +++ b/ui_framework/src/components/expression/_expression.scss @@ -47,8 +47,7 @@ transition: transform $globalAnimSpeedNormal $globalAnimSlightBounce, opacity $globalAnimSpeedNormal $globalAnimSlightBounce; // 1. Angulars ng-hide uses display: none. To use animations we need to use visibility instead. - &.ng-hide, - &.kuiExpressionItem__popover--isHidden { + &.ng-hide { display: block !important; // 1 visibility: hidden; opacity: 0; diff --git a/ui_framework/src/components/expression/expression_item.js b/ui_framework/src/components/expression/expression_item.js new file mode 100644 index 00000000000000..a1e35b625b9805 --- /dev/null +++ b/ui_framework/src/components/expression/expression_item.js @@ -0,0 +1,25 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +export const KuiExpressionItem = ({ + children, + className, + ...rest +}) => { + const classes = classNames('kuiExpressionItem', className); + + return ( +
+ {children} +
+ ); +}; + +KuiExpressionItem.propTypes = { + children: PropTypes.node, + className: PropTypes.string +}; diff --git a/ui_framework/src/components/expression/expression_item.test.js b/ui_framework/src/components/expression/expression_item.test.js new file mode 100644 index 00000000000000..74e7898cc63541 --- /dev/null +++ b/ui_framework/src/components/expression/expression_item.test.js @@ -0,0 +1,32 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../test/required_props'; + +import { + KuiExpressionItem, +} from './expression_item'; + +describe('KuiExpressionItem', () => { + test('renders', () => { + const component = ( + + ); + + expect(render(component)).toMatchSnapshot(); + }); + + describe('Props', () => { + describe('children', () => { + test('is rendered', () => { + const component = render( + + some expression + + ); + + expect(component) + .toMatchSnapshot(); + }); + }); + }); +}); diff --git a/ui_framework/src/components/expression/expression_item_button.js b/ui_framework/src/components/expression/expression_item_button.js new file mode 100644 index 00000000000000..4973f1953c6a32 --- /dev/null +++ b/ui_framework/src/components/expression/expression_item_button.js @@ -0,0 +1,35 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +export const KuiExpressionItemButton = ({ + className, + description, + buttonValue, + isActive, + onClick, + ...rest +}) => { + const classes = classNames('kuiExpressionItem__button', className, { + 'kuiExpressionItem__button--isActive': isActive + }); + + return ( + + ); +}; + +KuiExpressionItemButton.propTypes = { + className: PropTypes.string, + description: PropTypes.string.isRequired, + buttonValue: PropTypes.string.isRequired, + isActive: PropTypes.bool.isRequired, + onClick: PropTypes.func.isRequired, +}; diff --git a/ui_framework/src/components/expression/expression_item_button.test.js b/ui_framework/src/components/expression/expression_item_button.test.js new file mode 100644 index 00000000000000..27b03a7c33dc46 --- /dev/null +++ b/ui_framework/src/components/expression/expression_item_button.test.js @@ -0,0 +1,74 @@ +import React from 'react'; +import { render, shallow } from 'enzyme'; +import { requiredProps } from '../../test/required_props'; +import sinon from 'sinon'; + +import { + KuiExpressionItemButton, +} from './expression_item_button'; + +describe('KuiExpressionItemButton', () => { + test('renders', () => { + const component = ( + {}} + {...requiredProps} + /> + ); + + expect(render(component)).toMatchSnapshot(); + }); + + describe('Props', () => { + describe('isActive', () => { + test('true renders active', () => { + const component = ( + {}} + /> + ); + + expect(render(component)).toMatchSnapshot(); + }); + + test('false renders inactive', () => { + const component = ( + {}} + /> + ); + + expect(render(component)).toMatchSnapshot(); + }); + }); + + describe('onClick', () => { + test('is called when the button is clicked', () => { + const onClickHandler = sinon.spy(); + + const button = shallow( + + ); + + button.simulate('click'); + + sinon.assert.calledOnce(onClickHandler); + }); + }); + }); +}); diff --git a/ui_framework/src/components/expression/expression_item_popover.js b/ui_framework/src/components/expression/expression_item_popover.js new file mode 100644 index 00000000000000..f570997abbe349 --- /dev/null +++ b/ui_framework/src/components/expression/expression_item_popover.js @@ -0,0 +1,54 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { KuiOutsideClickDetector } from '../outside_click_detector'; + +const POPOVER_ALIGN = [ + 'left', + 'right', +]; + +const KuiExpressionItemPopover = ({ + className, + title, + children, + align, + onOutsideClick, + ...rest +}) => { + const classes = classNames('kuiExpressionItem__popover', className, { + 'kuiExpressionItem__popover--alignRight': align === 'right' + }); + return ( + +
+
+ {title} +
+
+ {children} +
+
+
+ ); +}; + +KuiExpressionItemPopover.defaultProps = { + align: 'left', +}; + +KuiExpressionItemPopover.propTypes = { + className: PropTypes.string, + title: PropTypes.string.isRequired, + children: PropTypes.node, + align: PropTypes.oneOf(POPOVER_ALIGN), + onOutsideClick: PropTypes.func.isRequired, +}; + +export { + POPOVER_ALIGN, + KuiExpressionItemPopover +}; diff --git a/ui_framework/src/components/expression/expression_item_popover.test.js b/ui_framework/src/components/expression/expression_item_popover.test.js new file mode 100644 index 00000000000000..0ffdd5315a3195 --- /dev/null +++ b/ui_framework/src/components/expression/expression_item_popover.test.js @@ -0,0 +1,68 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../test/required_props'; + +import { + KuiExpressionItemPopover, + POPOVER_ALIGN +} from './expression_item_popover'; + +describe('KuiExpressionItemPopover', () => { + test('renders', () => { + const component = ( + {}} + {...requiredProps} + /> + ); + + expect(render(component)).toMatchSnapshot(); + }); + + describe('Props', () => { + describe('children', () => { + test('is rendered', () => { + const component = render( + {}} + > + popover content + + ); + + expect(component).toMatchSnapshot(); + }); + }); + + describe('align', () => { + test('renders default', () => { + const component = render( + {}} + /> + ); + + expect(component).toMatchSnapshot(); + }); + + POPOVER_ALIGN.forEach(align => { + test(`renders the ${align} class`, () => { + const component = render( + {}} + /> + ); + + expect(component).toMatchSnapshot(); + }); + }); + }); + }); +}); diff --git a/ui_framework/src/components/expression/index.js b/ui_framework/src/components/expression/index.js new file mode 100644 index 00000000000000..0a98d7b5cd8cb1 --- /dev/null +++ b/ui_framework/src/components/expression/index.js @@ -0,0 +1,3 @@ +export { KuiExpressionItem } from './expression_item'; +export { KuiExpressionItemButton } from './expression_item_button'; +export { KuiExpressionItemPopover } from './expression_item_popover'; diff --git a/ui_framework/src/components/index.js b/ui_framework/src/components/index.js index 4e731c52282451..2b0b780eaee8fe 100644 --- a/ui_framework/src/components/index.js +++ b/ui_framework/src/components/index.js @@ -49,6 +49,12 @@ export { KuiEventBodyMetadata, } from './event'; +export { + KuiExpressionItem, + KuiExpressionItemButton, + KuiExpressionItemPopover, +} from './expression'; + export { KuiFieldGroup, KuiFieldGroupSection,