Building CampaignHawk: Styling Tooltips (Part 4)

Our sidenav looks good. Now it’s time for some hover-over tooltips. We’re going to do this with some CSS trickery, putting a rectangle and a triangle together. This is also going to take a few React events to trigger the tooltip.

Step 1: SidenavTooltip Component

We want our tooltip to look something like the image below. Just like our sidenav, this is almost entirely CSS.

We’re going to have to create a rectangle with a box shadow, then add a triangle to the left side of the rectangle. This is not as easy as it sounds and has to be done using CSS borders.

We should start by making the component:

SidenavTooltip = React.createClass({
render() {
tooltipStyle = {

}
return (
<div className="sidenav-tooltip" style={tooltipStyle}>
<p>Data Layer</p>
<div className="tail"></div>
</div>
)
}
})

In React, you pass inline styling using an object. We’re going to use this later to set the position of our tooltip. For now, what we have is a div with the class sidenav-tooltip and an inner div with the class tail.

Now for the styling. We want our tooltip 150 pixels wide for now and we want the content inside to be aligned. We also have to add this awkward 4 pixel padding to the bottom to make it look centered because align-items doesn’t pay attention to letters like “y” that have descenders. We also need to translateY by -50% to center it with mouse events.

.sidenav-tooltip {
transition: opacity 0.3s ease;
opacity: 0;
background-color: #fff;
height: 3em;
width: 150px;
padding-bottom: 4px;
margin-left: 30px;
position: absolute;
box-shadow: 0px 2px 8px 0px #888;
visibility: hidden;
display: none;
align-items: center;
transform: translateY(-50%);

Within sidenav-tooltip we need to style our text. I want to set a global default font color, so at the top of styles.scss, I’m going to set $default-text-color to a gray-blue.

$default-text-color: rgba(107,117,139,1);

Then I’ll make my text that color, pad it from the edges, and vertically align it to the center of the rectangle.

p {
color: $default-text-color;
font-size: 1.5em;
padding: 10px;
align-self: center;
}

And the last thing for styling is the triangle on the left, which we style using tail. Within sidenav-tooltip we want to give tail all transparent borders but one, which will have a white, 10px border. We will position it absolutely and move it down from the top so it’s roughly centered on the rectangle we created above.

.tail {
border: 10px solid;
border-color: transparent #fff transparent transparent;
position: absolute;
top: 13px;
left: -20px;
}

The next step is to make the tooltip appear on hover.

Step 2: Sidenav Refactor

We want our tooltips to appear on hover and disappear when the cursor is no longer hovering over an item. We’re going to set the position using inline styling and refactor our Sidenav component to make it more modular.

Let’s pull out the icon list and make a new component called SidenavIcons. Now that our icons are a separate component, we should set shouldComponentUpdate to make sure it doesn’t re-render our icons when we change state:

SidenavIcons = React.createClass({
shouldComponentUpdate(nextProps, nextState) {
return nextProps.id !== this.props.id
},

render() {
let iconList = [...]
let list = iconList.map((item) => {
return (
<li key={item} className="sidenav-list-item">
<i className={item}></i>
</li>
)
})
return (
<div>
{list}
</div>

)
}
})

And now we need to add a bunch of stuff to our Sidenav component since it is the parent component that will control whether or not the tooltip is being displayed. First, let’s getInitialState. We don’t want our tooltip showing when we start — only on hover. We also want to be able to change the y position of our tooltip. We’re going to fix the distance on the x-axis at 50px.

getInitialState() {
return {
showTooltip: false,
tooltipX: "50px",
tooltipY: "0px"
}
},

Then we need two functions: one for showing the tooltip, and one for hiding the tooltip. Since these will be attached to events, we’ll have access to the event (e) in our function. We’re going to take that event, find the target element (the icon being hovered over), find how far it is from the top of the screen (e.nativeEvent.target.offsetTop), find the height of the icon (e.nativeEvent.target.offsetHeight) and divide by 2 to get the middle, and finally add “px” to show that it’s in pixels:

showTooltip(e) {
this.setState({
showTooltip: true,
tooltipY: e.nativeEvent.target.offsetTop +
(e.nativeEvent.target.offsetHeight / 2) + "px"
})
},
hideTooltip(e) {
this.setState({
showTooltip: false
})
},

And then we need to change what we’re rendering in our Sidenav:

render() {
return (
<nav className="sidenav">
<SidenavTooltip />
<ul className="sidenav-list">
<SidenavIcons />
</ul>
</nav>
)
}

Next Steps

The next step is to connect our tooltips to our icons so they function properly. We want them to display to the right of the hovered-over icon and we want the content of the tooltip to be related to the icon. We also need to change the width of the tooltip to be the same as the content rather than a hard-coded value. Once we have our sidenav looking good, we can start working on modals, which will be a big part of this app.