mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
parent
aac1b250af
commit
e4a2bda4f2
@ -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;
|
||||
|
||||
|
16
public/app/core/components/Tooltip/Popover.jest.tsx
Normal file
16
public/app/core/components/Tooltip/Popover.jest.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
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();
|
||||
});
|
||||
});
|
34
public/app/core/components/Tooltip/Popover.tsx
Normal file
34
public/app/core/components/Tooltip/Popover.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
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);
|
16
public/app/core/components/Tooltip/Tooltip.jest.tsx
Normal file
16
public/app/core/components/Tooltip/Tooltip.jest.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
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();
|
||||
});
|
||||
});
|
45
public/app/core/components/Tooltip/Tooltip.tsx
Normal file
45
public/app/core/components/Tooltip/Tooltip.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
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);
|
@ -0,0 +1,16 @@
|
||||
// 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>
|
||||
`;
|
@ -0,0 +1,19 @@
|
||||
// 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>
|
||||
`;
|
57
public/app/core/components/Tooltip/withTooltip.tsx
Normal file
57
public/app/core/components/Tooltip/withTooltip.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
@ -1,104 +1,105 @@
|
||||
// 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';
|
||||
|
79
public/sass/components/_popper.scss
Normal file
79
public/sass/components/_popper.scss
Normal file
@ -0,0 +1,79 @@
|
||||
.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;
|
||||
}
|
11
yarn.lock
11
yarn.lock
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user