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`] = `
+
+`;
+
+exports[`KuiExpressionItemPopover Props align renders the left class 1`] = `
+
+`;
+
+exports[`KuiExpressionItemPopover Props align renders the right class 1`] = `
+
+`;
+
+exports[`KuiExpressionItemPopover Props children is rendered 1`] = `
+
+
+ title
+
+
+ popover content
+
+
+`;
+
+exports[`KuiExpressionItemPopover renders 1`] = `
+
+`;
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,