Просмотр исходного кода

panel-header: Updates for the new react-popper api and make it possible to hover the tooltip popper without it closing

Johannes Schill 7 лет назад
Родитель
Сommit
7d74ca4a8d

+ 11 - 26
public/app/core/components/Tooltip/Popover.tsx

@@ -1,34 +1,19 @@
-import React from 'react';
-import withTooltip from './withTooltip';
-import { Target } from 'react-popper';
+import React, { PureComponent } from 'react';
+import Popper from './Popper';
+import withPopper, { UsingPopperProps } from './withPopper';
 
-interface PopoverProps {
-  tooltipSetState: (prevState: object) => void;
-}
-
-class Popover extends React.Component<PopoverProps, any> {
-  constructor(props) {
-    super(props);
-    this.toggleTooltip = this.toggleTooltip.bind(this);
-  }
+class Popover extends PureComponent<UsingPopperProps> {
+  render() {
+    const { children, hidePopper, showPopper, className, ...restProps } = this.props;
 
-  toggleTooltip() {
-    const { tooltipSetState } = this.props;
-    tooltipSetState(prevState => {
-      return {
-        ...prevState,
-        show: !prevState.show,
-      };
-    });
-  }
+    const togglePopper = restProps.show === true ? hidePopper : showPopper;
 
-  render() {
     return (
-      <Target className="popper__target" onClick={this.toggleTooltip}>
-        {this.props.children}
-      </Target>
+      <div className={`popper__manager ${className}`} onClick={togglePopper}>
+        <Popper {...restProps}>{children}</Popper>
+      </div>
     );
   }
 }
 
-export default withTooltip(Popover);
+export default withPopper(Popover);

+ 42 - 0
public/app/core/components/Tooltip/Popper.tsx

@@ -0,0 +1,42 @@
+import React, { PureComponent } from 'react';
+import { Manager, Popper as ReactPopper, Reference } from 'react-popper';
+
+interface Props {
+  renderContent: (content: any) => any;
+  show: boolean;
+  placement?: any;
+  content: string | ((props: any) => JSX.Element);
+}
+
+class Popper extends PureComponent<Props> {
+  render() {
+    const { children, renderContent, show, placement } = this.props;
+    const { content } = this.props;
+    const modifiers = {
+      flip: { enabled: false },
+      preventOverflow: { enabled: false },
+      hide: { enabled: false },
+    };
+    return (
+      <Manager>
+        <Reference>{({ ref }) => <div ref={ref}>{children}</div>}</Reference>
+        {show && (
+          <ReactPopper placement={placement} modifiers={modifiers}>
+            {({ ref, style, placement, arrowProps }) => {
+              return (
+                <div ref={ref} style={style} data-placement={placement} className="popper">
+                  <div className="popper__background">
+                    {renderContent(content)}
+                    <div ref={arrowProps.ref} data-placement={placement} className="popper__arrow" />
+                  </div>
+                </div>
+              );
+            }}
+          </ReactPopper>
+        )}
+      </Manager>
+    );
+  }
+}
+
+export default Popper;

+ 9 - 28
public/app/core/components/Tooltip/Tooltip.tsx

@@ -1,36 +1,17 @@
 import React, { PureComponent } from 'react';
-import withTooltip from './withTooltip';
-import { Target } from 'react-popper';
-
-interface Props {
-  tooltipSetState: (prevState: object) => void;
-}
-
-class Tooltip extends PureComponent<Props> {
-  showTooltip = () => {
-    const { tooltipSetState } = this.props;
-
-    tooltipSetState(prevState => ({
-      ...prevState,
-      show: true,
-    }));
-  };
-
-  hideTooltip = () => {
-    const { tooltipSetState } = this.props;
-    tooltipSetState(prevState => ({
-      ...prevState,
-      show: false,
-    }));
-  };
+import Popper from './Popper';
+import withPopper, { UsingPopperProps } from './withPopper';
 
+class Tooltip extends PureComponent<UsingPopperProps> {
   render() {
+    const { children, hidePopper, showPopper, className, ...restProps } = this.props;
+
     return (
-      <Target className="popper__target" onMouseOver={this.showTooltip} onMouseOut={this.hideTooltip}>
-        {this.props.children}
-      </Target>
+      <div className={`popper__manager ${className}`} onMouseEnter={showPopper} onMouseLeave={hidePopper}>
+        <Popper {...restProps}>{children}</Popper>
+      </div>
     );
   }
 }
 
-export default withTooltip(Tooltip);
+export default withPopper(Tooltip);

+ 2 - 4
public/app/core/components/Tooltip/__snapshots__/Popover.test.tsx.snap

@@ -3,11 +3,9 @@
 exports[`Popover renders correctly 1`] = `
 <div
   className="popper__manager test-class"
+  onClick={[Function]}
 >
-  <div
-    className="popper__target"
-    onClick={[Function]}
-  >
+  <div>
     <button>
       Button with Popover
     </button>

+ 3 - 5
public/app/core/components/Tooltip/__snapshots__/Tooltip.test.tsx.snap

@@ -3,12 +3,10 @@
 exports[`Tooltip renders correctly 1`] = `
 <div
   className="popper__manager test-class"
+  onMouseEnter={[Function]}
+  onMouseLeave={[Function]}
 >
-  <div
-    className="popper__target"
-    onMouseOut={[Function]}
-    onMouseOver={[Function]}
-  >
+  <div>
     <a
       href="http://www.grafana.com"
     >

+ 83 - 0
public/app/core/components/Tooltip/withPopper.tsx

@@ -0,0 +1,83 @@
+import React from 'react';
+
+export interface UsingPopperProps {
+  showPopper: (prevState: object) => void;
+  hidePopper: (prevState: object) => void;
+  renderContent: (content: any) => any;
+  show: boolean;
+  placement?: string;
+  content: string | ((props: any) => JSX.Element);
+  className?: string;
+}
+
+interface Props {
+  placement?: string;
+  className?: string;
+  content: string | ((props: any) => JSX.Element);
+}
+
+interface State {
+  placement: string;
+  show: boolean;
+}
+
+export default function withPopper(WrappedComponent) {
+  return class extends React.Component<Props, State> {
+    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,
+          };
+        });
+      }
+    }
+
+    showPopper = () => {
+      this.setState(prevState => ({
+        ...prevState,
+        show: true,
+      }));
+    };
+
+    hidePopper = () => {
+      this.setState(prevState => ({
+        ...prevState,
+        show: false,
+      }));
+    };
+
+    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 { show, placement } = this.state;
+      return (
+        <WrappedComponent
+          {...this.props}
+          showPopper={this.showPopper}
+          hidePopper={this.hidePopper}
+          renderContent={this.renderContent}
+          show={show}
+          placement={placement}
+        />
+      );
+    }
+  };
+}

+ 0 - 58
public/app/core/components/Tooltip/withTooltip.tsx

@@ -1,58 +0,0 @@
-import React from 'react';
-import { Manager, Popper, Arrow } from 'react-popper';
-
-interface IwithTooltipProps {
-  placement?: string;
-  content: string | ((props: any) => JSX.Element);
-  className?: string;
-}
-
-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, className } = this.props;
-
-      return (
-        <Manager className={`popper__manager ${className || ''}`}>
-          <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>
-      );
-    }
-  };
-}

+ 14 - 10
public/sass/components/_popper.scss

@@ -1,12 +1,8 @@
 .popper {
   position: absolute;
   z-index: $zindex-tooltip;
-  background: $tooltipBackground;
   color: $tooltipColor;
   max-width: 400px;
-  border-radius: 3px;
-  box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
-  padding: 10px;
   text-align: center;
 }
 
@@ -35,10 +31,18 @@
   left: calc(50% - 5px);
   margin-top: 0;
   margin-bottom: 0;
+  padding-top: 5px;
 }
 
 .popper[data-placement^='bottom'] {
-  margin-top: 5px;
+  padding-top: 5px;
+}
+
+.popper__background {
+  background: $tooltipBackground;
+  border-radius: 3px;
+  box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
+  padding: 10px;
 }
 
 .popper[data-placement^='bottom'] .popper__arrow {
@@ -46,21 +50,21 @@
   border-left-color: transparent;
   border-right-color: transparent;
   border-top-color: transparent;
-  top: -5px;
-  left: calc(50% - 5px);
+  top: 0;
+  left: calc(50% - 8px);
   margin-top: 0;
   margin-bottom: 0;
 }
 .popper[data-placement^='right'] {
-  margin-left: 5px;
+  padding-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);
+  left: 0;
+  top: calc(50% - 8px);
   margin-left: 0;
   margin-right: 0;
 }