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 />',...