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.
Sam Corcos is the lead developer and co-founder of Sightline Maps, the most intuitive platform for 3D printing topographical maps, as well as LearnPhoenix.io, an advanced tutorial site for building scaleable production apps with Phoenix and React.
Cumulative Time
Additional
- Building CampaignHawk: An open-source election canvassing app with Meteor and React (Part 1)
- File Structure and Packages (Part 2)
- Styling the Sidenav (Part 3)
- Styling Tooltips (Part 4)
- Connecting Tooltips with React (Part 5)
- Making Modals (Part 6)
- Populating Modals (Part 7)
- Connecting Modals with React (Part 8)
- Mapbox and Data (Part 9)
- Thinking about Data (Part 10)
- Mapbox and GeoJSON (Part 11)
- Mapbox.js and Meteor Data (Part 12)
- Popouts and Radio Buttons (Part 13)
- Radio Button Styling (Part 14)
- Triggering Functions with Radio Buttons (Part 15)
- Spacial Analysis Overview (Part 16)
- Clustering with Leaflet Markercluster (Part 17)
- Making a Precinct Data Layer (Part 18)
- Scaling Colors and Values with D3 (Part 19)
- Styling the Precinct Data Layer (Part 20)
- Toggling Data Layers (Part 21)
- Voter Filter Data Layer (Part 22)