Skip to content

Commit

Permalink
feat(Tooltip): Adding a new Tooltip component
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenfitzpatrick committed Apr 15, 2018
1 parent f8603f0 commit d1e760d
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/components/Button/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Button.propTypes = {
};

Button.defaultProps = {
type: 'submit',
type: 'button',
use: 'Primary',
disabled: false,
fullWidth: false,
Expand Down
42 changes: 42 additions & 0 deletions src/components/Tooltip/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Basic :

```jsx
<Tooltip content="I am at the top Yo" id="topTooltip">
<Button>Top</Button>
</Tooltip>
```

```jsx
<Tooltip
open
placement="bottom"
content="But now I am at the bottom Yo"
id="bottomTooltip"
>
<Button>Bottom</Button>
</Tooltip>
```

```jsx
<Tooltip
open
placement="right"
content="I'm right Yo"
id="rightTooltip"
use="Info"
>
<Button>Right</Button>
</Tooltip>
```

```jsx
<Tooltip
open
placement="left"
content="Left wins the day tho !"
id="leftTooltip"
use="Error"
>
<Button>Left</Button>
</Tooltip>
```
141 changes: 141 additions & 0 deletions src/components/Tooltip/Tooltip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import PropTypes from 'prop-types';
import React from 'react';

import { TooltipArrow, TooltipInner, TooltipWrapper } from './Tooltip.styled';

class Tooltip extends React.Component {
childRef = React.createRef();
tooltipRef = React.createRef();

state = {
isHovered: false,
top: 0,
left: 0
};

_calculatePositions = () => {
const { placement } = this.props;
const arrowHeight = 15;
const {
offsetWidth: tooltipWidth,
offsetHeight: tooltipHeight
} = this.tooltipRef.current;
const {
offsetLeft,
offsetTop,
offsetHeight,
offsetWidth
} = this.childRef.current;

const difference = (tooltipWidth - offsetWidth) / 2;
const heightDifference = (tooltipHeight - offsetHeight) / 2;

let top, left;
switch (placement) {
case 'top':
top = offsetTop - tooltipHeight - arrowHeight;
left = offsetLeft - difference;
break;
case 'bottom':
top = offsetTop + offsetHeight + arrowHeight;
left = offsetLeft - difference;
break;
case 'right':
top = offsetTop - heightDifference;
left = offsetLeft + offsetWidth + arrowHeight;
break;
case 'left':
top = offsetTop - heightDifference;
left = offsetLeft - tooltipWidth - arrowHeight;
break;
}

this.setState({
top,
left
});
};

componentDidMount() {
this._calculatePositions();
}

toggle = () =>
requestAnimationFrame(() =>
this.setState(({ isHovered }) => ({
isHovered: !isHovered
}))
);

render() {
const { children, content, open, placement, id, use } = this.props;
const { isHovered, top, left } = this.state;
const visible = open ? true : isHovered;
return (
<React.Fragment>
<TooltipWrapper
left={left}
top={top}
show={visible}
innerRef={this.tooltipRef}
id={id}
use={use}
>
<TooltipInner use={use}>
<TooltipArrow placement={placement} />
{content}
</TooltipInner>
</TooltipWrapper>
{React.cloneElement(children, {
innerRef: this.childRef,
onMouseEnter: this.toggle,
onMouseLeave: this.toggle,
'aria-describedby': id
})}
</React.Fragment>
);
}
}

Tooltip.propTypes = {
/**
* ID for the tooltip, must be unique
*/
id: PropTypes.string.isRequired,
/**
* Select initial placement for the tooltip
*/
placement: PropTypes.oneOf(['top', 'bottom', 'left', 'right']),
/**
* Set theme
*/
use: PropTypes.oneOf([
'Primary',
'Secondary',
'Success',
'Warning',
'Error',
'Info',
'Light'
]),
/**
* Element to toggle tooltip
*/
children: PropTypes.node,
/**
* Tooltip content
*/
content: PropTypes.node,
/**
* Should tooltip always be visible ?
*/
open: PropTypes.bool
};

Tooltip.defaultProps = {
use: 'Primary',
placement: 'top',
open: false
};

export default Tooltip;
50 changes: 50 additions & 0 deletions src/components/Tooltip/Tooltip.styled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import styled from 'styled-components';

import { arrowSideMixin, colorMixin } from '../../styles/mixins';
import { font } from '../../styles/font';
import { spacing } from '../../styles/spacing';
import { transitionTiming } from '../../styles/animations';

export const Wrapper = styled.div`
display: contents;
`;

export const TooltipWrapper = styled.div.attrs({
role: 'tooltip',
'aria-hidden': p => !p.show
})`
position: absolute;
will-change: transform;
top: 0;
left: 0;
transform: ${p => `translateX(${p.left}px) translateY(${p.top}px)`};
transition: opacity ${transitionTiming};
opacity: ${p => (p.show ? 1 : 0)};
z-index: 10;
max-width: 12rem;
box-shadow: ${({ theme }) =>
`${theme.boxShadowSize} ${theme.boxShadowColor}`};
`;

export const TooltipInner = styled.div`
position: relative;
padding: ${spacing.s} ${spacing.m};
${colorMixin};
font-size: ${font.m};
text-align: center;
border-radius: 4px;
`;

export const TooltipArrow = styled.div`
position: absolute;
background-color: inherit;
width: 15px;
height: 15px;
transform: translateY(-50%) translateX(-50%) rotate(45deg);
${arrowSideMixin};
`;
24 changes: 24 additions & 0 deletions src/styles/mixins.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,27 @@ export const errorMixin = ({ warning }) => {
}
`;
};

export const arrowSideMixin = ({ placement }) => {
if (placement === 'top') {
return css`
top: 100%;
left: 50%;
`;
} else if (placement === 'bottom') {
return css`
top: 0%;
left: 50%;
`;
} else if (placement === 'right') {
return css`
top: 50%;
left: 0%;
`;
} else if (placement === 'left') {
return css`
top: 50%;
left: 100%;
`;
}
};
1 change: 1 addition & 0 deletions styleguide.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ module.exports = {
path.resolve(__dirname, 'src/components/Tag', 'Tag.js'),
path.resolve(__dirname, 'src/components/TagInput', 'TagInput.js'),
path.resolve(__dirname, 'src/components/Tabs', 'Tabs.js'),
path.resolve(__dirname, 'src/components/Tooltip', 'Tooltip.js'),
path.resolve(__dirname, 'src/components/Loader', 'Loader.js'),
path.resolve(__dirname, 'src/components/Icon', 'Icon.js')
];
Expand Down

0 comments on commit d1e760d

Please sign in to comment.