Building a Calendar using React JS, LESS CSS and Font Awesome

Published Feb 09, 2015Last updated Apr 12, 2017
Building a Calendar using React JS, LESS CSS and Font Awesome

Today, I'm going to talk about how to create a calendar control using React JS, LESS CSS, Font Awesome and Moment JS. I'm going to assume you're at least halfway familiar with these tools, but if you're not, I suggest you take a look using the links above. The calendar itself will be a React JS class that allows the user to select a date, which is set on a controller's property. I've styled the calendar, and I'll include that style in this tutorial, but you can obviously feel free to tweak how it looks to your heart's content. If you're the kind of reader that prefers to view a completed product and just check the source yourself, I've put together a demo page which shows off the calendar. You can see it here. This calendar control shouldn't be taken as a copy and paste solution to a problem you're having; it's meant as a guide to React itself as opposed to providing a production-ready calendar.

sample

Note: The CSS shown here might not match up with exactly what you see on the demo page because I want the demo to look at least halfway decent with some pretty styling. For the most part, though, I'm going to try to keep the CSS on the tutorial as bare as possible.

I've split the calendar control up into a few different classes: Calendar, DayNames and Week. The Calendar class contains the other two. DayNames is a simple header for the calendar which displays (unsurprisingly) week names, while the Week class is responsible for rendering each individual week.

Calendar

var Calendar = React.createClass({
  getInitialState: function() {
    return {
      month: this.props.selected.clone()
    };
  },

  previous: function() {
    var month = this.state.month;
    month.add(-1, "M");
    this.setState({ month: month });
  },

  next: function() {
    var month = this.state.month;
    month.add(1, "M");
    this.setState({ month: month });
  },

  select: function(day) {
    this.props.selected = day.date;
    this.forceUpdate();
  },

  render: function() {
    return <div>
      <div className="header">
        <i className="fa fa-angle-left" onClick={this.previous}></i>
        {this.renderMonthLabel()}
        <i className="fa fa-angle-right" onClick={this.next}></i>
      </div>
      <DayNames />
      {this.renderWeeks()}
    </div>;
  },

  renderWeeks: function() {
    var weeks = [],
      done = false,
      date = this.state.month.clone().startOf("month").add("w" -1).day("Sunday"),
      monthIndex = date.month(),
      count = 0;

    while (!done) {
      weeks.push(<Week key={date.toString()} date={date.clone()} month={this.state.month} select=    {this.select} selected={this.props.selected} />);
      date.add(1, "w");
      done = count++ > 2 && monthIndex !== date.month();
      monthIndex = date.month();
    }

    return weeks;
  },

  renderMonthLabel: function() {
    return <span>{this.state.month.format("MMMM, YYYY")}</span>;
  }
});

When rendering this class, the only prop it requires is the initially set date; everything else is derived from that. In our getInitialState method, we're setting the current month to be the same as the selected date. Month and selected date are two different variables to account for the user moving from February, for example, to March without selecting a new date. Outside of rendering, our Calendar class has three methods: previous, next and select. The previous and next methods are responsible for moving the user's calendar view from the current month to the previous and next month, respectively, while the select method takes care of marking a new date as the selected date. We need to perform a forceUpdate here to force a render of the view, as the Calendar class isn't watching the selected property.

The render method writes out some pretty standard HTML. Here, we're using Font Awesome to render some previous and next arrows, which wrap the current month's label. Then, we render the DayNames class, followed by the list of Week classes. There's some logic in there to allow for showing trailing and leading days, too.

DayNames

The DayNames class is the simplest of the three. It's sole responsibility is to render the names of the days near the top of the calendar.

var DayNames = React.createClass({
  render: function() {
    return <div className="week names">
      <span className="day">Sun</span>
      <span className="day">Mon</span>
      <span className="day">Tue</span>
      <span className="day">Wed</span>
      <span className="day">Thu</span>
       	<span className="day">Fri</span>
       	<span className="day">Sat</span>
    </div>;
  }
});

There's not really much to say about this. Note the use of className instead of class; it's an easy mistake to make when first starting out with React.

Week

var Week = React.createClass({
  render: function() {
    var days = [],
      date = this.props.date,
      month = this.props.month;

    for (var i = 0; i < 7; i++) {
      var day = {
        name: date.format("dd").substring(0, 1),
        number: date.date(),
        isCurrentMonth: date.month() === month.month(),
        isToday: date.isSame(new Date(), "day"),
        date: date
      };
      days.push(<span key={day.date.toString()} className={"day" + (day.isToday ? " today" : "") + (day.isCurrentMonth ? "" : " different-month") + (day.date.isSame(this.props.selected) ? " selected" : "")} onClick={this.props.select.bind(null, day)}>{day.number}</span>);
      date = date.clone();
      date.add(1, "d");

    }

    return <div className="week" key={days[0].toString()}>
      {days}
    </div>
  }
});

The Week class takes four props: date, month, select and selected. The first indicates the start of the week, while month gives us the current month for determining if we need trailing or leading days. The third and fourth props deal with selecting a date. The render method renders seven days, while setting various CSS classes on the day, one each for selected, today or trailing/leading days.

CSS

.vertical-centre (@height) {
    height:@height;
    line-height:@height !important;
    display:inline-block;
    vertical-align:middle;
}

.border-box {
    box-sizing:border-box;
    -moz-box-sizing:border-box;
}

@border-colour:#CCC;
calendar {
    float:left;
    display:block;
    .border-box;
    background:white;
    width:300px;
    border:solid 1px @border-colour;
    margin-bottom:10px;
    
    @secondary-colour:#2875C7;
    @spacing:10px;
    @icon-width:40px;
    @header-height:40px;

    >div.header {
        float:left;
        width:100%;
        background:@secondary-colour;
        height:@header-height;
        color:white;
        
        >* { 
            .vertical-centre(@header-height);
        }
        
        >i {
            float:left;
            width:@icon-width;
            font-size:1.125em;
            font-weight:bold;
            position:relative;
            .border-box;
            padding:0 @spacing;
            cursor:pointer;
        }
        
        >i.fa-angle-left {
            text-align:left;
        }
        
        >i.fa-angle-right {
            text-align:right;
            margin-left:@icon-width*-1;
        }
        
        >span { 
            float:left;
            width:100%;
            font-weight:bold;
            text-transform:uppercase;
            .border-box;
            padding-left:@icon-width+@spacing;
            margin-left:@icon-width*-1;
            text-align:center;
            padding-right:@icon-width;
            color:inherit;
        }
    }
    >div.week {
        float:left;
        width:100%;
        border-top:solid 1px @border-colour;
        
        &:first-child {
            border-top:none;
        }
        
        >span.day {
            float:left;
            width:100%/7;
            .border-box;
            border-left:solid 1px @border-colour;
            font-size:0.75em;
            text-align:center;
            .vertical-centre(30px);
            background:white;
            cursor:pointer;
            color:black;
            
            &:first-child {
                border-left:none;
            }
            
            &.today {
                background:#E3F2FF;
            }
            
            &.different-month {
                color:#C0C0C0;
            }
            
            &.selected {
                background:@secondary-colour;
                color:white;
            }
        }
        
        &.names>span {
            color:@secondary-colour;
            font-weight:bold;
        }
    }
}

The first few lines define a few helpful mixins using LESS CSS. The first allows us to centre elements vertically, presuming we have the height of the wrapping element. The second sets the box-sizing property, which Firefox doesn't support without the -moz- prefix.

Next, we're setting some variables related to styling and spacing that are used more than once. If you want to change the colour, for example, the secondary-colour variable is what you need to update. The rest is just standard styling to make the calendar look somewhat pretty and spacing stuff to make sure it's aligned properly.

Conclusion

And that's it! As I mentioned above, I've put together a demo page which shows off the calendar in its final form. You can see it here. Thanks for reading!

Discover and read more posts from Chris Harrington
get started
Enjoy this post?

Leave a like and comment for Chris