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 @@
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-grid-layout": "^0.16.1",
"react-popper": "^0.7.5",
"react-sizeme": "^2.3.6",
"remarkable": "^1.7.1",
"rxjs": "^5.4.3",
......
......@@ -23,6 +23,11 @@ export class AlertRuleList extends React.Component<IContainerProps, any> {
this.props.nav.load('alerting', 'alert-list');
this.fetchRules();
this.handleTooltipPositionChange = this.handleTooltipPositionChange.bind(this);
this.state = {
tooltipPosition: 'auto',
};
}
onStateFilterChanged = evt => {
......@@ -44,6 +49,12 @@ export class AlertRuleList extends React.Component<IContainerProps, any> {
});
};
handleTooltipPositionChange(evt) {
evt.preventDefault();
this.setState({
tooltipPosition: evt.target.value,
});
}
render() {
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
@import "../vendor/css/timepicker.css";
@import "../vendor/css/spectrum.css";
@import '../vendor/css/timepicker.css';
@import '../vendor/css/spectrum.css';
// MIXINS
@import "mixins/mixins";
@import "mixins/animations";
@import "mixins/buttons";
@import "mixins/breakpoints";
@import "mixins/grid";
@import "mixins/grid-framework";
@import "mixins/hover";
@import "mixins/forms";
@import "mixins/drop_element";
@import 'mixins/mixins';
@import 'mixins/animations';
@import 'mixins/buttons';
@import 'mixins/breakpoints';
@import 'mixins/grid';
@import 'mixins/grid-framework';
@import 'mixins/hover';
@import 'mixins/forms';
@import 'mixins/drop_element';
// BASE
@import "base/normalize";
@import "base/reboot";
@import "base/type";
@import "base/forms";
@import "base/grid";
@import "base/fonts";
@import "base/code";
@import "base/icons";
@import 'base/normalize';
@import 'base/reboot';
@import 'base/type';
@import 'base/forms';
@import 'base/grid';
@import 'base/fonts';
@import 'base/code';
@import 'base/icons';
// UTILS
@import "utils/utils";
@import "utils/validation";
@import "utils/angular";
@import "utils/spacings";
@import "utils/widths";
@import 'utils/utils';
@import 'utils/validation';
@import 'utils/angular';
@import 'utils/spacings';
@import 'utils/widths';
// LAYOUTS
@import "layout/lists";
@import "layout/page";
@import 'layout/lists';
@import 'layout/page';
// COMPONENTS
@import "components/scrollbar";
@import "components/cards";
@import "components/buttons";
@import "components/navs";
@import "components/tabs";
@import "components/alerts";
@import "components/switch";
@import "components/tooltip";
@import "components/tags";
@import "components/panel_graph";
@import "components/submenu";
@import "components/panel_alertlist";
@import "components/panel_dashlist";
@import "components/panel_gettingstarted";
@import "components/panel_pluginlist";
@import "components/panel_singlestat";
@import "components/panel_table";
@import "components/panel_text";
@import "components/panel_heatmap";
@import "components/panel_add_panel";
@import "components/settings_permissions";
@import "components/tagsinput";
@import "components/tables_lists";
@import "components/search";
@import "components/gf-form";
@import "components/sidemenu";
@import "components/navbar";
@import "components/timepicker";
@import "components/filter-controls";
@import "components/filter-list";
@import "components/filter-table";
@import "components/old_stuff";
@import "components/typeahead";
@import "components/modals";
@import "components/dropdown";
@import "components/color_picker";
@import "components/footer";
@import "components/infobox";
@import "components/shortcuts";
@import "components/drop";
@import "components/query_editor";
@import "components/tabbed_view";
@import "components/query_part";
@import "components/jsontree";
@import "components/edit_sidemenu";
@import "components/row.scss";
@import "components/json_explorer";
@import "components/code_editor";
@import "components/dashboard_grid";
@import "components/dashboard_list";
@import "components/page_header";
@import "components/dashboard_settings";
@import "components/empty_list_cta";
@import 'components/scrollbar';
@import 'components/cards';
@import 'components/buttons';
@import 'components/navs';
@import 'components/tabs';
@import 'components/alerts';
@import 'components/switch';
@import 'components/tooltip';
@import 'components/tags';
@import 'components/panel_graph';
@import 'components/submenu';
@import 'components/panel_alertlist';
@import 'components/panel_dashlist';
@import 'components/panel_gettingstarted';
@import 'components/panel_pluginlist';
@import 'components/panel_singlestat';
@import 'components/panel_table';
@import 'components/panel_text';
@import 'components/panel_heatmap';
@import 'components/panel_add_panel';
@import 'components/settings_permissions';
@import 'components/tagsinput';
@import 'components/tables_lists';
@import 'components/search';
@import 'components/gf-form';
@import 'components/sidemenu';
@import 'components/navbar';
@import 'components/timepicker';
@import 'components/filter-controls';
@import 'components/filter-list';
@import 'components/filter-table';
@import 'components/old_stuff';
@import 'components/typeahead';
@import 'components/modals';
@import 'components/dropdown';
@import 'components/color_picker';
@import 'components/footer';
@import 'components/infobox';
@import 'components/shortcuts';
@import 'components/drop';
@import 'components/query_editor';
@import 'components/tabbed_view';
@import 'components/query_part';
@import 'components/jsontree';
@import 'components/edit_sidemenu';
@import 'components/row.scss';
@import 'components/json_explorer';
@import 'components/code_editor';
@import 'components/dashboard_grid';
@import 'components/dashboard_list';
@import 'components/page_header';
@import 'components/dashboard_settings';
@import 'components/empty_list_cta';
@import 'components/popper';
// PAGES
@import "pages/login";
@import "pages/dashboard";
@import "pages/playlist";
@import "pages/admin";
@import "pages/alerting";
@import "pages/history";
@import "pages/plugins";
@import "pages/signup";
@import "pages/styleguide";
@import "pages/errorpage";
@import "old_responsive";
@import "components/view_states.scss";
@import 'pages/login';
@import 'pages/dashboard';
@import 'pages/playlist';
@import 'pages/admin';
@import 'pages/alerting';
@import 'pages/history';
@import 'pages/plugins';
@import 'pages/signup';
@import 'pages/styleguide';
@import 'pages/errorpage';
@import 'old_responsive';
@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:
version "1.0.0"
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:
version "0.1.1"
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:
react-draggable "^3.0.3"
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:
version "1.7.5"
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