JSFiddle - React, Tailwind, and code Playground

by ninty9notout

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/smooth-scroll/16.0.3/smooth-scroll.min.js"></script>
<div class="calendar calendar--hidden">
    <div class="calendar__header">
        <div class="month">
            <button class="month__previous" disabled>
                &#9664; <span class="visuallyhidden">Previous Month</span>
            </button>
            <h2 class="month__title">
                &nbsp;
            </h2>
            <button class="month__next" disabled>
                &#9654; <span class="visuallyhidden">Next Month</span>
            </button>
        </div>
        <div class="headings">
            <div class="headings__label">Mon</div>
            <div class="headings__label">Tue</div>
            <div class="headings__label">Wed</div>
            <div class="headings__label">Thu</div>
            <div class="headings__label">Fri</div>
            <div class="headings__label">Sat</div>
            <div class="headings__label">Sun</div>
        </div>
    </div>

    <div class="calendar__body days">
        <!-- intentionally empty -->
    </div>
    <!-- 
    <div class="calendar__overlay overlay">
        <div class="overlay__inner">
            <h2 class="overlay__title">Tuesday, 4th June, 2019</h2>
            
            <div class="overlay__events">
                <div class="overlay__event event">
                    <span class="event__time">7.30pm</span>
                    <span class="event__title">Hamlet</span>
                    <span class="event__action">
                        <a href="#" class="event__button">Buy Tickets</a>
                    </span>
                </div>
                <div class="overlay__event event">
                    <span class="event__time">7.30pm</span>
                    <span...

SCSS

html {
    box-sizing: border-box;
}

*, *::before, *::after {
    box-sizing: inherit;
}

body {
    margin: 0;
    font-family: sans-serif;
    font-size: 16px;
}

.visuallyhidden {
    position: absolute;
    overflow: hidden;
    clip: rect(0 0 0 0);
    height: 1px;
    width: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
}

.calendar--hidden {
    display: none;
}

.calendar {
    position: relative;
}

.calendar__header {
    position: sticky;
    z-index: 5;
    top: 0;
    background: #fff;
    box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.1);
}

.month {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 0.5rem 0;
    font-size: 0;
    cursor: default;
}

.month__previous,
.month__next {
    border: none;
    outline: none;
    background-color: transparent;
    padding: 0.25rem;
    margin: 0;
    font-size: 0.5rem;

    &[disabled] {
        visibility: hidden;
    }
}

.month__title {
    margin: 0;
    width: 50%;
    max-width: 300px;
    text-align: center;
    font-size: 1rem;
    font-weight: bold;
    text-transform: uppercase;
    cursor: default;
}

.headings {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    border-bottom: 1px solid #fcfcfc;
    font-size: 0;
    padding: 0.5rem 0;
}

.headings__label {
    text-align: center;
    font-weight: bold;
    text-transform: uppercase;
    font-size: 0.7rem;
    cursor: default;
}

.calendar__body {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    grid-gap: 1px;
    background-color: #eee;
}

.day {
    min-width: 0;
    min-height: 90px;
    padding: 10px 10px 5px;
}

.day--odd {
    background-color: #ffffff;
}

.day--even {
    background-color: #f9f9f9;
}

.day--past {
    background-color: #f2f2f2;
    cursor: not-allowed;
}

.day--today {
    background: repeating-linear-gradient(-45deg, #ffffff, #ffffff 10px, #f9f9f9 10px, #f9f9f9 20px);
}

.day__title {
    font-size: 0.8rem;
    margin: 0 0 0.25rem;
    pointer-events:...

JavaScript

// days start on monday
moment.updateLocale('en', {
    week: {
        dow: 1,
    },
});

const { flatten, orderBy, first, last, groupBy } = _;

const scroll = new SmoothScroll();

const today = moment();

const calendar = document.querySelector('.calendar');
const calendarHeader = document.querySelector('.calendar__header');
const previousMonthButton = document.querySelector('.month__previous');
const nextMonthButton = document.querySelector('.month__next');
const monthTitle = document.querySelector('.month__title');
const calendarBody = document.querySelector('.calendar__body');

const arrayWrap = (maybeArray) => {
	if (Array.isArray(maybeArray)) {
    	return maybeArray;
    }
    
    return [maybeArray];
}

const parseEvents = async () => {
	return fetch('https://api.myjson.com/bins/lbky7').then(response => {
    	return response.json();
    }).then(response => {
		let events = response.reduce((carry, event) => {
        	let instances = arrayWrap(event.Times.EventTime);

            instances = instances.map((instance) => {
            	const timeMoment = moment(instance.Time);
            	return {
                    event_id: parseInt(event.Id, 10),
                    instance_id: parseInt(instance.EventInstanceId, 10),
                    title: event.Name,
                    description: event.Description,
                    body: event.Html,
                    image_url: event.ImageUrl,
                    is_on_sale: instance.OnSaleOnWeb,
                    total_seats: parseInt(instance.Capacity, 10),
                    seats_available: parseInt(instance.SeatsAvailable, 10),
                    time: instance.Time,
                    time_moment: timeMoment,
                    duration: parseInt(event.Duration, 10),
                    day_group: timeMoment.format('YYYYMMDD'),
            	}
            });
            
            carry.push(instances);

            return carry;
        }, []);

		events = flatten(events);
        
     ...