Commit e4a2bda4 by Johannes Schill Committed by Torkel Ödegaard

10389 react tooltip components (#10473)

* poc: Use react-popper for tooltips #10389

* poc: Add popover component and use a hoc() for Tooltip + Popover to avoid code duplication #10389

* jest: Add snapshot tests to Popover and Tooltip #10389

* poc: Move target from hoc into Popover/Tooltip-component #10389

* poc: Clean up unused styles and use the existing Grafana style/colors on popper tooltip #10389

* poc: Remove test code before PR

* poc: Remove imports used in poc but shouldn't be included anymore #10389
parent aac1b250
...@@ -149,6 +149,7 @@ ...@@ -149,6 +149,7 @@
"react": "^16.2.0", "react": "^16.2.0",
"react-dom": "^16.2.0", "react-dom": "^16.2.0",
"react-grid-layout": "^0.16.1", "react-grid-layout": "^0.16.1",
"react-popper": "^0.7.5",
"react-sizeme": "^2.3.6", "react-sizeme": "^2.3.6",
"remarkable": "^1.7.1", "remarkable": "^1.7.1",
"rxjs": "^5.4.3", "rxjs": "^5.4.3",
......
...@@ -23,6 +23,11 @@ export class AlertRuleList extends React.Component<IContainerProps, any> { ...@@ -23,6 +23,11 @@ export class AlertRuleList extends React.Component<IContainerProps, any> {
this.props.nav.load('alerting', 'alert-list'); this.props.nav.load('alerting', 'alert-list');
this.fetchRules(); this.fetchRules();
this.handleTooltipPositionChange = this.handleTooltipPositionChange.bind(this);
this.state = {
tooltipPosition: 'auto',
};
} }
onStateFilterChanged = evt => { onStateFilterChanged = evt => {
...@@ -44,6 +49,12 @@ export class AlertRuleList extends React.Component<IContainerProps, any> { ...@@ -44,6 +49,12 @@ export class AlertRuleList extends React.Component<IContainerProps, any> {
}); });
}; };
handleTooltipPositionChange(evt) {
evt.preventDefault();
this.setState({
tooltipPosition: evt.target.value,
});
}
render() { render() {
const { nav, alertList } = this.props; const { nav, alertList } = this.props;
......
import React from 'react';
import renderer from 'react-test-renderer';
import Popover from './Popover';
describe('Popover', () => {
it('renders correctly', () => {
const tree = renderer
.create(
<Popover placement="auto" content="Popover text">
<button>Button with Popover</button>
</Popover>
)
.toJSON();
expect(tree).toMatchSnapshot();
});
});
import React from 'react';
import withTooltip from './withTooltip';
import { Target } from 'react-popper';
interface IPopoverProps {
tooltipSetState: (prevState: object) => void;
}
class Popover extends React.Component<IPopoverProps, any> {
constructor(props) {
super(props);
this.toggleTooltip = this.toggleTooltip.bind(this);
}
toggleTooltip() {
const { tooltipSetState } = this.props;
tooltipSetState(prevState => {
return {
...prevState,
show: !prevState.show,
};
});
}
render() {
return (
<Target className="popper__target" onClick={this.toggleTooltip}>
{this.props.children}
</Target>
);
}
}
export default withTooltip(Popover);
import React from 'react';
import renderer from 'react-test-renderer';
import Tooltip from './Tooltip';
describe('Tooltip', () => {
it('renders correctly', () => {
const tree = renderer
.create(
<Tooltip placement="auto" content="Tooltip text">
<a href="http://www.grafana.com">Link with tooltip</a>
</Tooltip>
)
.toJSON();
expect(tree).toMatchSnapshot();
});
});
import React from 'react';
import withTooltip from './withTooltip';
import { Target } from 'react-popper';
interface ITooltipProps {
tooltipSetState: (prevState: object) => void;
}
class Tooltip extends React.Component<ITooltipProps, any> {
constructor(props) {
super(props);
this.showTooltip = this.showTooltip.bind(this);
this.hideTooltip = this.hideTooltip.bind(this);
}
showTooltip() {
const { tooltipSetState } = this.props;
tooltipSetState(prevState => {
return {
...prevState,
show: true,
};
});
}
hideTooltip() {
const { tooltipSetState } = this.props;
tooltipSetState(prevState => {
return {
...prevState,
show: false,
};
});
}
render() {
return (
<Target className="popper__target" onMouseOver={this.showTooltip} onMouseOut={this.hideTooltip}>
{this.props.children}
</Target>
);
}
}
export default withTooltip(Tooltip);
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Popover renders correctly 1`] = `
<div
className="popper__manager"
>
<div
className="popper__target"
onClick={[Function]}
>
<button>
Button with Popover
</button>
</div>
</div>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Tooltip renders correctly 1`] = `
<div
className="popper__manager"
>
<div
className="popper__target"
onMouseOut={[Function]}
onMouseOver={[Function]}
>
<a
href="http://www.grafana.com"
>
Link with tooltip
</a>
</div>
</div>
`;
import React from 'react';
import { Manager, Popper, Arrow } from 'react-popper';
interface IwithTooltipProps {
placement?: string;
content: string | ((props: any) => JSX.Element);
}
export default function withTooltip(WrappedComponent) {
return class extends React.Component<IwithTooltipProps, any> {
constructor(props) {
super(props);
this.setState = this.setState.bind(this);
this.state = {
placement: this.props.placement || 'auto',
show: false,
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.placement && nextProps.placement !== this.state.placement) {
this.setState(prevState => {
return {
...prevState,
placement: nextProps.placement,
};
});
}
}
renderContent(content) {
if (typeof content === 'function') {
// If it's a function we assume it's a React component
const ReactComponent = content;
return <ReactComponent />;
}
return content;
}
render() {
const { content } = this.props;
return (
<Manager className="popper__manager">
<WrappedComponent {...this.props} tooltipSetState={this.setState} />
{this.state.show ? (
<Popper placement={this.state.placement} className="popper">
{this.renderContent(content)}
<Arrow className="popper__arrow" />
</Popper>
) : null}
</Manager>
);
}
};
}
// vendor // vendor
@import "../vendor/css/timepicker.css"; @import '../vendor/css/timepicker.css';
@import "../vendor/css/spectrum.css"; @import '../vendor/css/spectrum.css';
// MIXINS // MIXINS
@import "mixins/mixins"; @import 'mixins/mixins';
@import "mixins/animations"; @import 'mixins/animations';
@import "mixins/buttons"; @import 'mixins/buttons';
@import "mixins/breakpoints"; @import 'mixins/breakpoints';
@import "mixins/grid"; @import 'mixins/grid';
@import "mixins/grid-framework"; @import 'mixins/grid-framework';
@import "mixins/hover"; @import 'mixins/hover';
@import "mixins/forms"; @import 'mixins/forms';
@import "mixins/drop_element"; @import 'mixins/drop_element';
// BASE // BASE
@import "base/normalize"; @import 'base/normalize';
@import "base/reboot"; @import 'base/reboot';
@import "base/type"; @import 'base/type';
@import "base/forms"; @import 'base/forms';
@import "base/grid"; @import 'base/grid';
@import "base/fonts"; @import 'base/fonts';
@import "base/code"; @import 'base/code';
@import "base/icons"; @import 'base/icons';
// UTILS // UTILS
@import "utils/utils"; @import 'utils/utils';
@import "utils/validation"; @import 'utils/validation';
@import "utils/angular"; @import 'utils/angular';
@import "utils/spacings"; @import 'utils/spacings';
@import "utils/widths"; @import 'utils/widths';
// LAYOUTS // LAYOUTS
@import "layout/lists"; @import 'layout/lists';
@import "layout/page"; @import 'layout/page';
// COMPONENTS // COMPONENTS
@import "components/scrollbar"; @import 'components/scrollbar';
@import "components/cards"; @import 'components/cards';
@import "components/buttons"; @import 'components/buttons';
@import "components/navs"; @import 'components/navs';
@import "components/tabs"; @import 'components/tabs';
@import "components/alerts"; @import 'components/alerts';
@import "components/switch"; @import 'components/switch';
@import "components/tooltip"; @import 'components/tooltip';
@import "components/tags"; @import 'components/tags';
@import "components/panel_graph"; @import 'components/panel_graph';
@import "components/submenu"; @import 'components/submenu';
@import "components/panel_alertlist"; @import 'components/panel_alertlist';
@import "components/panel_dashlist"; @import 'components/panel_dashlist';
@import "components/panel_gettingstarted"; @import 'components/panel_gettingstarted';
@import "components/panel_pluginlist"; @import 'components/panel_pluginlist';
@import "components/panel_singlestat"; @import 'components/panel_singlestat';
@import "components/panel_table"; @import 'components/panel_table';
@import "components/panel_text"; @import 'components/panel_text';
@import "components/panel_heatmap"; @import 'components/panel_heatmap';
@import "components/panel_add_panel"; @import 'components/panel_add_panel';
@import "components/settings_permissions"; @import 'components/settings_permissions';
@import "components/tagsinput"; @import 'components/tagsinput';
@import "components/tables_lists"; @import 'components/tables_lists';
@import "components/search"; @import 'components/search';
@import "components/gf-form"; @import 'components/gf-form';
@import "components/sidemenu"; @import 'components/sidemenu';
@import "components/navbar"; @import 'components/navbar';
@import "components/timepicker"; @import 'components/timepicker';
@import "components/filter-controls"; @import 'components/filter-controls';
@import "components/filter-list"; @import 'components/filter-list';
@import "components/filter-table"; @import 'components/filter-table';
@import "components/old_stuff"; @import 'components/old_stuff';
@import "components/typeahead"; @import 'components/typeahead';
@import "components/modals"; @import 'components/modals';
@import "components/dropdown"; @import 'components/dropdown';
@import "components/color_picker"; @import 'components/color_picker';
@import "components/footer"; @import 'components/footer';
@import "components/infobox"; @import 'components/infobox';
@import "components/shortcuts"; @import 'components/shortcuts';
@import "components/drop"; @import 'components/drop';
@import "components/query_editor"; @import 'components/query_editor';
@import "components/tabbed_view"; @import 'components/tabbed_view';
@import "components/query_part"; @import 'components/query_part';
@import "components/jsontree"; @import 'components/jsontree';
@import "components/edit_sidemenu"; @import 'components/edit_sidemenu';
@import "components/row.scss"; @import 'components/row.scss';
@import "components/json_explorer"; @import 'components/json_explorer';
@import "components/code_editor"; @import 'components/code_editor';
@import "components/dashboard_grid"; @import 'components/dashboard_grid';
@import "components/dashboard_list"; @import 'components/dashboard_list';
@import "components/page_header"; @import 'components/page_header';
@import "components/dashboard_settings"; @import 'components/dashboard_settings';
@import "components/empty_list_cta"; @import 'components/empty_list_cta';
@import 'components/popper';
// PAGES // PAGES
@import "pages/login"; @import 'pages/login';
@import "pages/dashboard"; @import 'pages/dashboard';
@import "pages/playlist"; @import 'pages/playlist';
@import "pages/admin"; @import 'pages/admin';
@import "pages/alerting"; @import 'pages/alerting';
@import "pages/history"; @import 'pages/history';
@import "pages/plugins"; @import 'pages/plugins';
@import "pages/signup"; @import 'pages/signup';
@import "pages/styleguide"; @import 'pages/styleguide';
@import "pages/errorpage"; @import 'pages/errorpage';
@import "old_responsive"; @import 'old_responsive';
@import "components/view_states.scss"; @import 'components/view_states.scss';
.popper {
position: absolute;
background: $tooltipBackground;
color: $tooltipColor;
width: 150px;
border-radius: 3px;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
padding: 10px;
text-align: center;
}
.popper .popper__arrow {
width: 0;
height: 0;
border-style: solid;
position: absolute;
margin: 5px;
}
.popper .popper__arrow {
border-color: $tooltipBackground;
}
.popper[data-placement^='top'] {
margin-bottom: 5px;
}
.popper[data-placement^='top'] .popper__arrow {
border-width: 5px 5px 0 5px;
border-left-color: transparent;
border-right-color: transparent;
border-bottom-color: transparent;
bottom: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
.popper[data-placement^='bottom'] {
margin-top: 5px;
}
.popper[data-placement^='bottom'] .popper__arrow {
border-width: 0 5px 5px 5px;
border-left-color: transparent;
border-right-color: transparent;
border-top-color: transparent;
top: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
.popper[data-placement^='right'] {
margin-left: 5px;
}
.popper[data-placement^='right'] .popper__arrow {
border-width: 5px 5px 5px 0;
border-left-color: transparent;
border-top-color: transparent;
border-bottom-color: transparent;
left: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
}
.popper[data-placement^='left'] {
margin-right: 5px;
}
.popper[data-placement^='left'] .popper__arrow {
border-width: 5px 0 5px 5px;
border-top-color: transparent;
border-right-color: transparent;
border-bottom-color: transparent;
right: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
}
.popper__target,
.popper__manager {
display: inline-block;
}
...@@ -7504,6 +7504,10 @@ pn@^1.0.0: ...@@ -7504,6 +7504,10 @@ pn@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/pn/-/pn-1.0.0.tgz#1cf5a30b0d806cd18f88fc41a6b5d4ad615b3ba9" resolved "https://registry.yarnpkg.com/pn/-/pn-1.0.0.tgz#1cf5a30b0d806cd18f88fc41a6b5d4ad615b3ba9"
popper.js@^1.12.5:
version "1.12.9"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.12.9.tgz#0dfbc2dff96c451bb332edcfcfaaf566d331d5b3"
posix-character-classes@^0.1.0: posix-character-classes@^0.1.0:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
...@@ -8123,6 +8127,13 @@ react-grid-layout@^0.16.1: ...@@ -8123,6 +8127,13 @@ react-grid-layout@^0.16.1:
react-draggable "^3.0.3" react-draggable "^3.0.3"
react-resizable "^1.7.5" react-resizable "^1.7.5"
react-popper@^0.7.5:
version "0.7.5"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-0.7.5.tgz#71c25946f291db381231281f6b95729e8b801596"
dependencies:
popper.js "^1.12.5"
prop-types "^15.5.10"
react-resizable@^1.7.5: react-resizable@^1.7.5:
version "1.7.5" version "1.7.5"
resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-1.7.5.tgz#83eb75bb3684da6989bbbf4f826e1470f0af902e" resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-1.7.5.tgz#83eb75bb3684da6989bbbf4f826e1470f0af902e"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment