This commit is contained in:
Dominik Prokop
2019-01-08 20:51:00 +01:00
parent 22c9ce7de8
commit 79c6fdc0e8
19 changed files with 46 additions and 28 deletions

View File

@@ -11,6 +11,8 @@
"license": "ISC",
"dependencies": {
"@torkelo/react-select": "2.1.1",
"@types/react-test-renderer": "^16.0.3",
"@types/react-transition-group": "^2.0.15",
"classnames": "^2.2.5",
"jquery": "^3.2.1",
"lodash": "^4.17.10",
@@ -23,11 +25,11 @@
"react-virtualized": "^9.21.0"
},
"devDependencies": {
"@types/classnames": "^2.2.6",
"@types/jest": "^23.3.2",
"@types/jquery": "^1.10.35",
"@types/lodash": "^4.14.119",
"@types/react": "^16.7.6",
"@types/classnames": "^2.2.6",
"@types/jquery": "^1.10.35",
"typescript": "^3.2.2"
}
}

View File

@@ -0,0 +1,69 @@
import React, { PureComponent } from 'react';
import * as PopperJS from 'popper.js';
import { Manager, Popper as ReactPopper } from 'react-popper';
import Portal from 'app/core/components/Portal/Portal';
import Transition from 'react-transition-group/Transition';
const defaultTransitionStyles = {
transition: 'opacity 200ms linear',
opacity: 0,
};
const transitionStyles = {
exited: { opacity: 0 },
entering: { opacity: 0 },
entered: { opacity: 1 },
exiting: { opacity: 0 },
};
interface Props extends React.DOMAttributes<HTMLDivElement> {
renderContent: (content: any) => any;
show: boolean;
placement?: PopperJS.Placement;
content: string | ((props: any) => JSX.Element);
refClassName?: string;
referenceElement: PopperJS.ReferenceObject;
}
class Popper extends PureComponent<Props> {
render() {
const { renderContent, show, placement, onMouseEnter, onMouseLeave } = this.props;
const { content } = this.props;
return (
<Manager>
<Transition in={show} timeout={100} mountOnEnter={true} unmountOnExit={true}>
{transitionState => (
<Portal>
<ReactPopper placement={placement} referenceElement={this.props.referenceElement}>
{({ ref, style, placement, arrowProps }) => {
return (
<div
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
ref={ref}
style={{
...style,
...defaultTransitionStyles,
...transitionStyles[transitionState],
}}
data-placement={placement}
className="popper"
>
<div className="popper__background">
{renderContent(content)}
<div ref={arrowProps.ref} data-placement={placement} className="popper__arrow" />
</div>
</div>
);
}}
</ReactPopper>
</Portal>
)}
</Transition>
</Manager>
);
}
}
export default Popper;

View File

@@ -0,0 +1,94 @@
import React from 'react';
import * as PopperJS from 'popper.js';
type PopperContent = string | (() => JSX.Element);
export interface UsingPopperProps {
show?: boolean;
placement?: PopperJS.Placement;
content: PopperContent;
children: JSX.Element;
renderContent?: (content: PopperContent) => JSX.Element;
}
type PopperControllerRenderProp = (
showPopper: () => void,
hidePopper: () => void,
popperProps: {
show: boolean;
placement: PopperJS.Placement;
content: string | ((props: any) => JSX.Element);
renderContent: (content: any) => any;
}
) => JSX.Element;
interface Props {
placement?: PopperJS.Placement;
content: PopperContent;
className?: string;
children: PopperControllerRenderProp;
}
interface State {
placement: PopperJS.Placement;
show: boolean;
}
class PopperController extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
placement: this.props.placement || 'auto',
show: false,
};
}
componentWillReceiveProps(nextProps: Props) {
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: PopperContent) {
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 { children, content } = this.props;
const { show, placement } = this.state;
return children(this.showPopper, this.hidePopper, {
show,
placement,
content,
renderContent: this.renderContent,
});
}
}
export default PopperController;

View File

@@ -0,0 +1,18 @@
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 className="test-class" href="http://www.grafana.com">
Link with tooltip
</a>
</Tooltip>
)
.toJSON();
expect(tree).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,32 @@
import React, { createRef } from 'react';
import * as PopperJS from 'popper.js';
import Popper from './Popper';
import PopperController, { UsingPopperProps } from './PopperController';
export const Tooltip = ({ children, renderContent, ...controllerProps }: UsingPopperProps) => {
const tooltipTriggerRef = createRef<PopperJS.ReferenceObject>();
return (
<PopperController {...controllerProps}>
{(showPopper, hidePopper, popperProps) => {
return (
<>
{tooltipTriggerRef.current && (
<Popper
{...popperProps}
onMouseEnter={showPopper}
onMouseLeave={hidePopper}
referenceElement={tooltipTriggerRef.current}
/>
)}
{React.cloneElement(children, {
ref: tooltipTriggerRef,
onMouseEnter: showPopper,
onMouseLeave: hidePopper,
})}
</>
);
}}
</PopperController>
);
};

View File

@@ -0,0 +1,114 @@
$popper-margin-from-ref: 5px;
.popper {
position: absolute;
z-index: $zindex-tooltip;
color: $tooltipColor;
max-width: 400px;
text-align: center;
}
.popper .popper__arrow {
width: 0;
height: 0;
border-style: solid;
position: absolute;
margin: 0px;
}
.popper .popper__arrow {
border-color: $tooltipBackground;
}
.popper__background {
background: $tooltipBackground;
border-radius: $border-radius;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
padding: 10px;
}
// Top
.popper[data-placement^='top'] {
padding-bottom: $popper-margin-from-ref;
}
.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);
padding-top: 5px;
}
// Bottom
.popper[data-placement^='bottom'] {
padding-top: $popper-margin-from-ref;
}
.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: 0;
left: calc(50% - 5px);
}
.popper[data-placement^='bottom-start'] {
padding-top: $popper-margin-from-ref;
}
.popper[data-placement^='bottom-start'] .popper__arrow {
border-width: 0 5px 5px 5px;
border-left-color: transparent;
border-right-color: transparent;
border-top-color: transparent;
top: 0;
left: 5px;
}
.popper[data-placement^='bottom-end'] {
padding-top: $popper-margin-from-ref;
}
.popper[data-placement^='bottom-end'] .popper__arrow {
border-width: 0 5px 5px 5px;
border-left-color: transparent;
border-right-color: transparent;
border-top-color: transparent;
top: 0;
left: calc(100% - 5px);
}
// Right
.popper[data-placement^='right'] {
padding-left: $popper-margin-from-ref;
}
.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: 0;
top: calc(50% - 5px);
}
// Left
.popper[data-placement^='left'] {
padding-right: $popper-margin-from-ref;
}
.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);
}
.popper__target,
.popper__manager {
display: inline-block;
}
.popper__manager--block {
display: block;
}

View File

@@ -0,0 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Tooltip renders correctly 1`] = `
<a
className="test-class"
href="http://www.grafana.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Link with tooltip
</a>
`;

View File

@@ -1 +1,2 @@
@import 'DeleteButton/DeleteButton';
@import 'Tooltip/Tooltip';

View File

@@ -1 +1,2 @@
export { DeleteButton } from './DeleteButton/DeleteButton';
export { Tooltip } from './Tooltip/Tooltip';