Having a functional application is one thing, but at a certain point, the application’s UI needs some attention. In this tutorial, we’ll be pulling in Blueprint.js to help us clean up our UI.
Goals
- Install blueprint.js into our existing React application.
- Utilize the blueprint’s UI toolkit to improve the look of the application.
Since React applications are made of components from top to bottom, we have the ability to pull in components that other people have already created. We’ve already done this by pulling in [React Router][2], but those components are functional rather than being visual. Being able to leverage Open Source components can help move a project along (especially if you’re not great with CSS). Blueprint.js is a mix of both CSS components and JavaScript components, and we’ll use both.
Installing Blueprint.js
We can’t utilize the Blueprint components until we install the package and import the components. Let’s install the package from NPM (using yarn
):
$ yarn add @blueprint/core
You should see a message someone in the yarn output about having ‘unmet peer dependencies’ because we haven’t installed react-addons-css-transition-group
, so let’s also install that.
$ yarn add react-addons-css-transition-group
With those two packages successfully installed we’re ready to use Blueprint, but we also need to import the styles into our index.js
.
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import 'normalize.css/normalize.css';
import '@blueprintjs/core/dist/blueprint.css';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
Adding Navigation
Not all of the components in Blueprint are JavaScript components, some of them are plain old HTML and CSS. The navigation component is one of those, and we’ll use it to replace our Header
component’s content. Here’s the final Header
component.
src/components/Header.js
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
class Header extends Component {
render() {
return (
<nav className="pt-navbar">
<div className="pt-navbar-group pt-align-left">
<div className="pt-navbar-heading">Chord Creator</div>
<input className="pt-input" placeholder="Search songs..." type="text" />
</div>
<div className="pt-navbar-group pt-align-right">
<Link className="pt-button pt-minimal pt-icon-music" to="/songs">Songs</Link>
<span className="pt-navbar-divider"></span>
<button className="pt-button pt-minimal pt-icon-user"></button>
<button className="pt-button pt-minimal pt-icon-cog"></button>
</div>
</nav>
);
}
}
export default Header;
Styling the Song List
Now that we’ve added a little bit of navigation we can click something from anywhere on the site and get back to the song list. Let’s style the song list to make it a little more presentable. The first thing that we’re going to do is extract the listing of songs into a separate component.
src/components/SongList.js
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
const songListStyles = {
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "center",
}
const songCardStyles = {
maxWidth: "30%",
minWidth: "150px",
flex: "1",
margin: "5px",
}
class SongList extends Component {
render() {
const { songs } = this.props
const songIds = Object.keys(songs)
return (
<div>
<h1 style={{marginBottom: "0.5em"}}>Songs</h1>
<div style={songListStyles}>
{songIds.map((id) => {
const song = songs[id]
return (
<div key={id} style={songCardStyles} className="pt-card pt-elevation-0 pt-interactive">
<h5><Link to={`/songs/${song.id}`}>{song.title}</Link></h5>
</div>
)
})}
</div>
</div>
)
}
}
export default SongList
Style wise we did a few different things here. We’ve created some JavaScript objects for songListStyles
and songCardStyles
that we then use to set inline styles on the divs containing our songs. These will be written out as inline styles when the markup is rendered, but we don’t need to interact with them in code like they are strings. For the h1
and the wrapper div
we set some inline styles directly. Lastly, we’re using the pt-card
component from Blueprint and rending the song’s title. Next, we need to utilize this new SongList
component in App
:
src/components/App.js
// Remainder of file omitted
render() {
return (
<div style={{maxWidth: "1160px", margin: "0 auto"}}>
<BrowserRouter>
<div>
<Header />
<div className="main-content" style={{padding: "1em"}}>
<div className="workspace">
<Route exact path="/songs" render={(props) => {
return <SongList songs={this.state.songs} />
}} />
<Route path="/songs/:songId" render={(props) => {
const song = this.state.songs[props.match.params.songId];
return (
song
? <ChordEditor song={song} updateSong={this.updateSong} />
: <h1>Song not found</h1>
)
}} />
</div>
</div>
<Footer />
</div>
</BrowserRouter>
</div>
);
}
// omitted
We’ve left most of the file out because the only changes we made outside of render
were that we imported the SongList
component and remove the import of Link
since it’s no longer used in this file. We substitute the map
logic that we had before with SongList
and added a few inline styles to the wrapper div to help the application breath a little.
Cleaning up the ChordEditor UI
The main portion of our application is the ChordEditor
, and the bulk of the changes remaining in this application revolve around it, but in this tutorial, we’re only going to clean up the area around the editor.
src/components/ChordEditor.js
import React, { Component } from 'react';
import { Breadcrumb } from '@blueprintjs/core';
import ChordSheetJS from 'chordsheetjs';
class ChordEditor extends Component {
// Other functions omitted, unchanged
render() {
return (
<div>
<ul className="pt-breadcrumbs">
<li><Breadcrumb href="/songs" text="Songs" /></li>
<li><Breadcrumb href="#" text={this.props.song.title} /></li>
</ul>
<h2 style={{margin: "0.5em 0"}}>{this.props.song.title}</h2>
<div className="chord-editor">
<div className="panel">
<h3>Input</h3>
<textarea
style={{width: "100%", height: "100%"}}
onChange={this.handleChange}
value={this.props.song.chordpro}/>
</div>
<div className="panel">
<h3>Output</h3>
<div
style={{width: "100%", height: "100%", fontFamily: "monospace"}}
className="chord-output"
dangerouslySetInnerHTML={this.getChordMarkup()}/>
</div>
</div>
</div>
);
}
}
export default ChordEditor;
The biggest change here is that we’ve added the song title to the page and we’ve utilized another component of Blueprint.js to build breadcrumbs. The Breadcrumb
component itself is pretty simple and outputs an anchor tag to go into our breadcrumb ul > li
that we’ve decorated with the Blueprint classes. These changes aren’t huge, but they’ll make it easier to know which song is being worked on at the moment.
Recap
We took a look at how we can integrate external styles into our application by pulling in Blueprint.js and did a little bit of work to clean up the UI. It is much easier to work on an application once it has a little bit of aesthetic appeal and personality, so these changes will make working with this application better as we move forward.