Sailing menu

by tedbeer

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/zepto/1.2.0/zepto.min.js"></script>
<script src="https://fonts.googleapis.com/css?family=PT Sans"></script>
<script src="https://fonts.googleapis.com/css?family=Raleway"></script>
<div id="menu" class="menu__top">
	<span class="menu__current-trip">Croatia 2013</span>
	<pop-up class="menu__trips-container">
		<ul class="menu__trips-list">
			<li data="" class="selected empty">Select the trip</li>
			<li data="xxx.json">Sailing XXX</li>
			<li data="2013-opening.json">Sailing season 2013</li>
			<li data="2013.json">Croatia 2013</li>
			<li data="2016">France 2016</li>
			<li data="2013">8 Croatia 2013</li>
			<li data="2016">7 France 2016</li>
			<li data="2013">6 Croatia 2013</li>
			<li data="2016">5 France 2016</li>
			<li data="2013">4 Croatia 2013</li>
			<li data="2016">3 France 2016</li>
			<li data="2013">2 Croatia 2013</li>
			<li data="2016">1 France 2016</li>
		</ul>
	</pop-up>
	<div class="menu__day-container">
		<ul class="menu__day-list">
			<li date="25082013">day 2: Aug 25</li>
			<li date="26082013">day 3: Aug 26</li>
		</ul>
	</div>
</div>

SCSS

$module: '.menu';
$ttime: 5s;

#{$module} {

  &__top {
		color: #336;  
		position: absolute;
		top: 100px;	
		margin-left: -1.4em;
		left: 100%;
		margin-right: 1.8em;
		transition: left $ttime ease;

		&.menu-open {
			right: 0;
			left: auto;
		}

		&.menu-anim {
			transition: all $ttime ease;
		}
  }

	&__current-trip {
		display: block;
		cursor: default;
		user-select: none;
		transform: rotateZ(-90deg);
		transform-origin: 100% 0.1em;
		position: absolute;
		right: 100%;
		white-space: nowrap;
		margin-top: 0.5em;
		max-width: 150px;
		overflow: hidden;
		text-overflow: ellipsis;
		border: 2px solid #d96;
		border-radius: 1.2em 1.2em 0 0;
		border-bottom: none;
		padding: 0.2em 1.2em;
		background: #E83;
		color: #fff;
		font-family: 'Raleway';
		/* font-weight: bold; */
}
	
	&__day-container {
  	display: none;
  }

	&__trips-list {
		list-style: none;
		border: 1px dotted #CCC;
		margin: 0;
		padding: 0;
		cursor: default;
		
		& li {
			white-space: nowrap;
			padding: 2px 5px 0;
		}
	}

	&__trips-list {
		top: 0;
		overflow: hidden;
		max-height: 1.2em;	

		& li.empty {
			color: #CCC;
		}
	}

	&__trips-container {
		position: relative;
		left: 1.6em;

		&.open li {
			&.selected {
				background-color: #FC9;
			}				
			&:hover {
				background-color: #F96;
			}
			
			&.empty {
				color: transparent;
			}
		}
	}

	.open > &__trips-list {
		overflow: auto;
		max-height: 250px;
		z-index: 1;
		background-color: white;
		padding-top: 0.5em;
		padding-bottom: 0.5em;
	}

.red {
    background: #F99;
    color: white;
  }
}

JavaScript

'use strict';

function menu(config, container) {
  this.menu = container;
	this.title = $('<span />', {class: 'menu__current-trip'});
	this.trips = $('<ul />', {class: 'menu__trips-list'});
	this.popup = $('<pop-up />', {class: 'menu__trips-container'});
	this.popup.append(this.trips);
	_.each(config, item => {
  	this.trips.append(this.createItem(item));
  });
	this.menu.empty();
	this.menu.append(this.title);
	this.menu.append(this.popup);

	this.title.on('click',
		e => this.toggleMenu());

	this.menu.on('transitionend',
		() => this.menu.removeClass('menu-anim'));
		
	this.trips.on('click', e => {
		switch(e.target.localName) {
			case 'li':
			 if (this.isListOpen()) {
		   	this.select($(e.target));
			 } else {
			 	this.openList();
			 }
			 break;
		}
	});
	this.select($('.menu__trips-list li:first-child', this.menu));
}

menu.prototype = {
	toggleMenu: function() {
		let off = this.menu.offset();
		let bClose = this.menu.hasClass('menu-open');
		if (bClose) {
			this.menu.toggleClass('menu-open');
			this.menu[0].style.left = '';
		} else {
			this.menu.toggleClass('menu-open');
		  let offEnd = this.menu.offset();
			
			this.menu[0].style.left = (offEnd.left + 20).toFixed(2) + 'px';
			this.menu[0].style.transform = 'translateX(' + (off.left - offEnd.left).toFixed(2) + 'px)';
			requestAnimationFrame(() => {
				this.menu.addClass('menu-anim');
				this.menu[0].style.transform = '';
			});
		}
	},
  isListOpen: function() {
		return this.popup.hasClass('open');
	},
	openList: function() {
		this.popup.addClass('open');
	},
	closeList: function() {
		this.popup.removeClass('open');
	},
	select: function(el) {
		let elm = el[0];
		this.closeList();
		$('.selected', this.popup).removeClass('selected');
		el.addClass('selected');
		elm.parentNode.scrollTop = elm.offsetTop;
		this.title.text(el.text());
		this.load(el.attr('data'));
	},
	load: function() {
	
	},
  createItem: function (cfg) {
  	let css = cfg.class || '';
    let li = $('<li />',...