Skip to content

Instantly share code, notes, and snippets.

@mattborn
Created August 14, 2014 22:23
Show Gist options
  • Save mattborn/251d5e9fa2ac422b1940 to your computer and use it in GitHub Desktop.
Save mattborn/251d5e9fa2ac422b1940 to your computer and use it in GitHub Desktop.
Menu, Pages + Dialogs UI

Experimental responsive interface.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Menu, Pages + Dialogs UI</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<style>
/* Helpers */
$cinema: "(min-width: 1441px)";
$desktop: "(max-width: 1024px)";
$tablet: "(max-width: 768px)";
$phone: "(max-width: 568px)";
/* Reset */
*,
*::before,
*::after { box-sizing: border-box; }
body { margin: 0; }
p { margin: 0; }
button {
background: 0;
border: 0;
cursor: pointer;
font: inherit;
margin: 0;
overflow: visible;
padding: 0;
text-align: inherit;
}
[hidden] { display: none; }
/* Base */
html,
body { height: 100%; }
button {
min-width: 120px;
background: rgba(0,0,0,.1);
border-radius: 15px;
color: inherit;
display: inline-block;
font-size: 13px;
letter-spacing: inherit;
line-height: 30px;
margin: 5px;
padding: 0 15px;
text-align: center;
vertical-align: top;
}
/* UI */
.ui {
height: 100%;
color: rgba(0,0,0,.7);
font: 100%/1 'Proxima Nova', 'Helvetica Neue', Arial, sans-serif;
-webkit-font-smoothing: antialiased;
letter-spacing: .02em;
overflow: hidden;
padding-left: 200px;
text-rendering: optimizeLegibility;
-webkit-text-size-adjust: none;
-webkit-transform: translateZ(0);
-webkit-transition: .5s cubic-bezier(.2,1,.2,1);
&-text {
font-size: 18px;
font-weight: 600;
line-height: 40px;
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translateX(-50%) translateY(-20px);
}
&-theme {
position: absolute;
bottom: 10px;
right: 10px;
}
}
%sticky {
width: 100%;
height: 40px;
line-height: 40px;
position: absolute;
top: 0;
left: 0;
}
%scroll {
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.menu {
@extend %scroll;
width: 200px;
height: 100%;
position: absolute;
top: 0;
left: 0;
-webkit-transition: inherit;
&-content {
height: auto;
min-height: 100%;
margin-bottom: -200px;
padding-bottom: 200px;
position: relative;
}
&-body { position: relative; }
&-foot {
width: 100%;
height: 200px;
position: absolute;
bottom: 0;
left: 0;
}
}
/* Pages */
.pages {
width: 100%;
height: 100%;
position: relative;
left: 0;
-webkit-transition: inherit;
}
.page {
&-top,
&-head { @extend %sticky; }
&-top { display: none; }
&-content {
@extend %scroll;
height: 100%;
position: relative;
}
&-body {
max-width: 700px;
margin: 0 auto;
position: relative;
}
}
%single {
width: 100%;
height: 100%;
background: #fff;
position: relative;
}
%series {
width: 50%;
height: 100%;
position: absolute;
-webkit-transition: inherit;
}
.aaa {
&-page {
@extend %series;
left: 0;
}
}
.bbb {
&-page {
@extend %series;
left: 50%;
.back-button,
.back-button-ccc { display: none; }
}
}
.ccc {
&-page {
@extend %series;
padding: 0;
left: 100%;
.page-top { display: block; }
}
}
.ddd {
&-page { @extend %single; }
}
.eee {
&-page { @extend %single; }
}
.dialog {
@extend %scroll;
width: 100%;
height: 100%;
background: rgba(0,0,0,.7);
position: absolute;
top: 0;
left: 0;
text-align: center;
&::before {
width: 1px;
height: 100%;
content: '';
display: inline-block;
margin-right: -1px;
vertical-align: middle;
}
&-top {
@extend %sticky;
background: #fff;
display: none;
text-align: initial;
}
&-content {
min-width: 320px;
max-width: 720px;
background: #fff;
display: inline-block;
position: relative;
text-align: left;
vertical-align: middle;
}
&-head,
&-body,
&-foot {
padding: 20px;
position: relative;
}
}
/* States */
.menu-open .menu { left: 0; }
.bbb-open .aaa-page .next-button,
.ccc-open .bbb-page .next-button { display: none; }
.ccc-open .bbb-page .back-button-ccc { display: block; }
.ccc-open .aaa-page { left: -50%; }
.ccc-open .bbb-page { left: 0; }
.ccc-open .ccc-page { left: 50%; }
.eee-open .ddd-page { display: none; }
/* Responsive */
@media #{$cinema} {
.menu { z-index: 2; }
.aaa-page {
width: 620px;
z-index: 1;
}
.bbb-page {
width: 100%;
left: 0;
padding-left: 620px;
z-index: 0;
}
.ccc-open .bbb-page {
width: 50%;
padding-left: 0;
}
}
@media #{$desktop} {
.ui { padding: 0; }
.menu { left: -200px; }
.menu-open .pages { left: 200px; }
.page-top { display: block; }
.aaa-page,
.bbb-page,
.ddd-page,
.eee-page { padding-top: 40px; }
.ccc-page .back-button { display: none; }
}
@media #{$tablet} {
.ui { padding-left: 200px; }
.menu { left: 0; }
.menu-open .pages { left: 0; }
.aaa-page .back-button,
.ddd-page .back-button,
.eee-page .page-top .back-button { display: none; }
.bbb-page .back-button,
.ccc-page .back-button { display: block; }
.aaa-page,
.bbb-page,
.ccc-page { width: 100%; }
.bbb-page { left: 100%; }
.bbb-open .aaa-page { left: -100%; }
.bbb-open .bbb-page { left: 0; }
.ccc-open .bbb-page { left: -100%; }
.ccc-open .ccc-page { left: 0; }
}
@media #{$phone} {
.ui { padding: 0; }
.menu { left: -200px; }
.menu-open .pages { left: 200px; }
.menu-open .pane-body,
.menu-open .page-body { pointer-events: none; }
.aaa-page .back-button,
.ddd-page .back-button,
.eee-page .page-top .back-button { display: block; }
.dialog { padding-top: 40px; }
.dialog-top { display: block; }
.dialog-content {
width: 100%;
min-height: 100%;
}
}
/* Themes */
.bare {
.menu,
.dialog-head {
background: #333;
color: #fff;
}
.menu-foot { border-top: 1px solid #444; }
.page-top,
.page-head { border-bottom: 1px solid #eee; }
.aaa-page,
.bbb-page { border-right: 1px solid #eee; }
.dialog-foot { border-top: 1px solid #eee; }
}
.fresh {
.menu,
.dialog-head,
.ddd-page .page-top,
.eee-page .page-top {
background: #345;
color: #fff;
}
.aaa-page { background: #eee; }
.bbb-page { border-right: 1px solid #eee; }
.aaa-page .page-top,
.bbb-page .page-top {
background: #eee;
border-bottom: 1px solid #ddd;
}
.ccc-page .page-top {
background: #0be;
color: #fff;
}
.ddd-page .page-head,
.eee-page .page-head { background: #eee; }
.dialog-foot {
background: #4b6;
color: #fff;
}
}
/* Demo only */
.menu-body,
.page-body,
.dialog-body { min-height: 400px; }
.menu-body,
.menu-foot,
.page-body,
.a-page .page-body,
.b-page .page-body,
.c-page .page-body { padding: 20px; }
</style>
<script src="//cdnjs.cloudflare.com/ajax/libs/sass.js/0.4.0/sass.min.js"></script>
<script>
// this allows the styles above to retain css code coloring in a
// text editor instead of having everything in this script tag
var head = document.getElementsByTagName('head')[0],
style = document.getElementsByTagName('style')[0],
css = Sass.compile(style.innerHTML);
head.removeChild(style);
document.write("<style>"+ css +"</style>");
</script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react/0.11.0/react-with-addons.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react/0.11.0/JSXTransformer.js"></script>
<script type="text/jsx">
/** @jsx React.DOM */
var UI = React.createClass({
getInitialState: function () {
return {
menu_open: false,
bbb_open: false,
ccc_open: false,
ddd_open: false,
eee_open: false,
dialog_open: false,
theme: 'bare'
};
},
render: function () {
var classes = React.addons.classSet({
'ui': true,
'menu-open': this.state.menu_open,
'bbb-open': this.state.bbb_open,
'ccc-open': this.state.ccc_open,
'ddd-open': this.state.ddd_open,
'eee-open': this.state.eee_open,
'dialog-open': this.state.dialog_open
});
classes += ' '+ this.state.theme;
return (
<div className={classes}>
<div className="pages">
<div className="aaa-page">
<div className="page-content">
<div className="page-body">
<button className="next-button" data-target="bbb" onClick={this.open}>Open Page B</button>
<p className="ui-text">Page A Body</p>
</div>
</div>
<div className="page-top">
<button className="back-button" data-target="menu" onClick={this.toggle}>Toggle Menu</button>
<p className="ui-text">Page A Top</p>
</div>
</div>
<div className="bbb-page">
<div className="page-content" hidden={!this.state.bbb_open}>
<div className="page-body">
<button className="next-button" data-target="ccc" onClick={this.open}>Open Page C</button>
<p className="ui-text">Page B Body</p>
</div>
</div>
<div className="page-top">
<button className="back-button" data-target="bbb" onClick={this.close}>Close Page B</button>
<button className="back-button-ccc" data-target="ccc" onClick={this.close}>Close Page C</button>
<p className="ui-text">Page B Top</p>
</div>
</div>
<div className="ccc-page">
<div className="page-content" hidden={!this.state.ccc_open}>
<div className="page-body">
<p className="ui-text">Page C Body</p>
</div>
</div>
<div className="page-top">
<button className="back-button" data-target="ccc" onClick={this.close}>Close Page C</button>
<p className="ui-text">Page C Top</p>
</div>
</div>
<div className="ddd-page" hidden={!this.state.ddd_open}>
<div className="page-content">
<div className="page-body">
<p className="ui-text">Page D Body</p>
</div>
<div className="page-head">
<button className="next-button" data-target="eee" onClick={this.open}>Open Page E</button>
<p className="ui-text">Page D Head</p>
</div>
</div>
<div className="page-top">
<button className="back-button" data-target="menu" onClick={this.toggle}>Toggle Menu</button>
<p className="ui-text">Page D Top</p>
</div>
</div>
<div className="eee-page" hidden={!this.state.eee_open}>
<div className="page-content">
<div className="page-body">
<p className="ui-text">Page E Body</p>
</div>
<div className="page-head">
<button className="back-button" data-target="eee" onClick={this.close}>Open Page D</button>
<p className="ui-text">Page E Head</p>
</div>
</div>
<div className="page-top">
<button className="back-button" data-target="menu" onClick={this.toggle}>Toggle Menu</button>
<p className="ui-text">Page E Top</p>
</div>
</div>
</div>
<div className="menu">
<div className="menu-content">
<div className="menu-body">
<button data-target="ddd" onClick={this.close}>Open Page A</button>
<button data-target="ddd" onClick={this.open}>Open Page D</button>
<p className="ui-text">Menu Body</p>
</div>
<div className="menu-foot">
<button data-target="dialog" onClick={this.open}>Open Dialog</button>
<p className="ui-text">Menu Foot</p>
</div>
</div>
</div>
<div className="dialog" hidden={!this.state.dialog_open} data-target="dialog" onClick={this.toggle}>
<div className="dialog-content" onClick={this.stopPropagation}>
<div className="dialog-head">
<p className="ui-text">Dialog Head</p>
</div>
<div className="dialog-body">
<p className="ui-text">Dialog Body</p>
</div>
<div className="dialog-foot">
<p className="ui-text">Dialog Foot</p>
</div>
</div>
<div className="dialog-top">
<button className="back-button" data-target="dialog" onClick={this.close}>Close Dialog</button>
<p className="ui-text">Dialog Top</p>
</div>
</div>
<button className="ui-theme" onClick={this.theme}>ϟ</button>
</div>
);
},
stopPropagation: function (ev) {
return false;
},
open: function (ev) {
var target = ev.currentTarget.dataset.target,
open = {};
open[target +'_open'] = true;
this.setState(open);
this.checks(target);
},
close: function (ev) {
var target = ev.currentTarget.dataset.target,
close = {};
close[target +'_open'] = false;
this.setState(close);
this.checks(target);
},
toggle: function (ev) {
var target = ev.currentTarget.dataset.target;
if (this.state[target +'_open']) {
this.close(ev);
} else {
this.open(ev);
}
},
checks: function (target) {
if (target !== 'menu') {
this.setState({menu_open: false});
}
if (target === 'ddd') {
this.setState({eee_open: false});
}
},
theme: function () {
var themes = ['bare', 'fresh', 'null'],
current = themes.indexOf(this.state.theme),
n = themes.length - 1;
if (current++ < n) {
this.setState({theme: themes[current]});
} else {
this.setState({theme: themes[0]});
}
}
});
React.renderComponent(<UI />, document.body);
</script>
</head>
<body>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment