// Fonts
@import 'https://fonts.googleapis.com/css?family=Lato:100,300,400,700,900';
@import 'https://fonts.googleapis.com/css?family=Playfair+Display:400,700,900';
// Colors
$goldfusion: #7B7554;
$yankeesblue: #17183B;
$darkmagenta: #A11692;
$fieryrose: #FF4F79;
$peachorange: #FFB49A;
body {
background: $yankeesblue;
color: $yankeesblue;
font-family: 'Lato', sans-serif;
}
.App {
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
left: 0;
background: $yankeesblue;
.Overlay {
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
left: 0;
background-size: cover;
filter: blur(10px);
opacity: .2;
}
}
// Header
header {
position: fixed;
top: 0;
left: 0;
width: 100vw;
background: white;
z-index: 10;
padding: 20px;
}
// Container
.Container {
display: flex;
height: 532px;
max-width: 650px;
z-index: 3;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
}
// ImagePreview
.ImagePreview {
background-size: cover;
display: flex;
align-items: flex-end;
position: relative;
width: 50%;
&::before {
width: 100%;
content: '';
display: block;
position: absolute;
top: 0;
left: 0;
height: 100%;
background: -moz-linear-gradient(top, rgba($yankeesblue,0) 0%, rgba($yankeesblue,0.45) 100%); /* FF3.6-15 */
background: -webkit-linear-gradient(top, rgba($yankeesblue,0) 0%,rgba($yankeesblue,0.45) 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(to bottom, rgba($yankeesblue,0) 0%,rgba($yankeesblue,0.45) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#a6000000',GradientType=0 ); /* IE6-9 */
}
.WorkspaceOverview {
color: white;
position: relative;
z-index: 3;
width: 100%;
padding: 20px;
.WorkspaceInformation {
.WorkspaceName {
font-family: 'Playfair Display';
font-weight: 600;
font-size: 36px;
}
.WorkspacePrice {
display: flex;
align-items: flex-end;
margin-top: 20px;
.Price {
font-size: 26px;
font-weight: 200;
}
.Duration {
font-size: 16px;
font-weight: 300;
position: relative;
bottom: 2px;
margin-left: 5px;
}
}
}
.WorkspaceMeta {
color: white;
line-height: 1.6;
font-size: 13px;
font-weight: 300;
margin-top: 20px;
strong {
font-weight: 600;
}
}
}
}
// Checkout
.Checkout {
background: white;
width: 50%;
.OrderSummary {
border-bottom: 1px solid darken(white, 5);
padding-bottom: 10px;
table {
font-size: 12px;
margin: 0 20px;
margin-bottom: 10px;
line-height: 1.6;
width: calc(100% - 40px);
opacity: .8;
font-weight: 400;
tr {
td:last-child {
text-align: right;
font-weight: 600;
}
}
}
.Total {
display: flex;
padding: 0 20px;
margin-bottom: 10px;
align-items: flex-end;
.TotalLabel {
display: block;
text-transform: uppercase;
margin-bottom: 5px;
font-size: 11px;
font-weight: 600;
}
.Amount {
font-size: 18px;
margin-left: auto;
}
}
}
.Title {
font-family: 'Playfair Display';
font-size: 18px;
padding: 20px;
}
}
// Inputs
.BasicInput {
padding: 0 20px;
margin-bottom: 20px;
label {
display: block;
text-transform: uppercase;
margin-bottom: 5px;
font-size: 11px;
font-weight: 600;
}
input {
width: 100%;
border: 0;
border-bottom: 1px solid $yankeesblue;
font-family: 'Lato', sans-serif;
padding: 5px 0;
font-size: 18px;
font-weight: 300;
outline: none;
&:focus {
border-bottom: 1px solid $peachorange;
}
}
}
// Expiry Date
.ExpiryDate {
display: flex;
margin-bottom: 20px;
padding: 0 20px;
width: 100%;
box-sizing: border-box;
flex-wrap: nowrap;
label {
display: block;
text-transform: uppercase;
margin-bottom: 5px;
font-size: 11px;
font-weight: 600;
}
div:first-child {
flex: 1 0 auto;
}
.Expiry {
display: flex;
}
input {
width: 100%;
border: 0;
border-bottom: 1px solid $yankeesblue;
font-family: 'Lato', sans-serif;
padding: 5px 0;
font-size: 18px;
font-weight: 300;
outline: none;
}
select {
appearance: none;
flex: 1 0 auto;
padding: 10px;
border-radius: 0;
border: 0;
background: transparent;
border-bottom: 1px solid $yankeesblue;
border-bottom: 1px solid $yankeesblue;
font-family: 'Lato', sans-serif;
padding: 5px 0;
font-size: 18px;
font-weight: 300;
outline: none;
margin-right: 10px;
}
.CVCField {
width: 20%;
}
}
// CVC
.CVC {
display: flex;
margin-bottom: 20px;
padding: 0 20px;
label {
display: block;
text-transform: uppercase;
margin-bottom: 5px;
font-size: 11px;
font-weight: 600;
}
input {
width: 100%;
border: 0;
border-bottom: 1px solid $yankeesblue;
font-family: 'Lato', sans-serif;
padding: 5px 0;
font-size: 18px;
font-weight: 300;
outline: none;
&:focus {
border-bottom: 1px solid $peachorange;
}
}
span {
display: block;
padding-top: 20px;
margin-left: 10px;
color: $peachorange;
font-size: 11px;
border-bottom: 1px dotted $peachorange;
}
}
// Checkout Button
.CheckoutButton {
padding: 0 20px;
text-align: center;
button {
background: darken($peachorange, 5);
color: white;
border: 0;
border-radius: 2px;
font-family: 'Lato', sans-serif;
width: 100%;
font-size: 13px;
text-transform: uppercase;
font-weight: 300;
outline: none;
padding: 10px 0;
margin-bottom: 10px;
&:hover {
background: darken($peachorange, 10);
}
&:active {
transform: scale(.99);
}
}
span {
font-size: 11px;
color: darken(white, 25);
font-weight: 300;
text-align: center;
}
}
// Animations
.overlay-enter {
opacity: 0.01;
}
.overlay-enter.overlay-enter-active {
opacity: .2;
transition: opacity .5s ease .5s;
}
.container-enter {
opacity: 0.01;
}
.container-enter.container-enter-active {
opacity: 1;
transition: opacity .5s ease;
}
View Compiled
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
var Overlay = React.createClass({
render: function() {
return (
<div className="Overlay" style={{'backgroundImage':'url(' + this.props.image + ')'}}>
Something
</div>
);
}
});
var Container = React.createClass({
render: function() {
return (
<div className="Container">
{this.props.children}
</div>
)
}
});
var WorkspaceInformation = React.createClass({
render: function() {
if(this.props.duration > 1) {
var duration = this.props.duration + ' days';
} else {
var duration = this.props.duration + ' day';
}
return (
<div className="WorkspaceInformation">
<div className="WorkspaceName">{this.props.name}</div>
<div className="WorkspacePrice">
<div className="Price">{this.props.price} GBP</div>
<div className="Duration">/ {duration}</div>
</div>
</div>
);
}
});
var WorkspaceMeta = React.createClass({
render: function() {
if(this.props.people > 1) {
var people = this.props.people + ' people';
} else {
var people = this.props.people + ' person';
}
return (
<div className="WorkspaceMeta">
<div className="Description">Entire office for <strong>{people}</strong></div>
<div className="Dates"><strong>Mon, Aug 22, 2016</strong> to <strong>Fri, Aug 29, 2016</strong></div>
</div>
);
}
});
var ImagePreview = React.createClass({
render: function() {
return (
<div className="ImagePreview" style={{'backgroundImage': 'url('+ this.props.image +')'}}>
<div className="WorkspaceOverview">
<WorkspaceInformation name="Coworking Space, South Korea" price={this.props.price} duration="1" />
<WorkspaceMeta people={this.props.people} />
</div>
</div>
);
}
});
var OrderSummary = React.createClass({
render: function() {
// Duration pluralisation
if(this.props.duration > 1) {
var duration = this.props.duration + ' days';
} else {
var duration = this.props.duration + ' day';
}
// Initial total Calculation
var initialTotal = this.props.duration * this.props.price;
// Discount Calculation
var discount = Math.floor((initialTotal / 100) * this.props.discount);
// Subtotal (with Discount)
var subTotal = initialTotal - discount;
// Calculate tax
var tax = Math.floor((subTotal / 100) * this.props.tax);
// Total
var total = subTotal + tax;
return (
<div className="OrderSummary">
<div className="Title">Order Summary</div>
<table>
<tr>
<td>{this.props.price} x {duration}</td>
<td>{initialTotal} GBP</td>
</tr>
<tr>
<td>Discount</td>
<td>{discount} GBP</td>
</tr>
<tr>
<td>Subtotal</td>
<td>{subTotal} GBP</td>
</tr>
<tr>
<td>Tax</td>
<td>{tax} GBP</td>
</tr>
</table>
<div className="Total">
<div className="TotalLabel">Total</div>
<div className="Amount">
{total} <small>GBP</small>
</div>
</div>
</div>
);
}
});
var PaymentForm = React.createClass({
render: function() {
return (
<div className="PaymentForm">
<form onSubmit={this.props.onSubmit}>
<div className="Title">Payment information</div>
<BasicInput name="name" label="Name on credit card" type="text" placeholder="John Smith" />
<BasicInput name="card" label="Credit card number" type="number" placeholder="0000 0000 0000 0000" />
<ExpiryDate />
<CheckoutButton />
</form>
</div>
);
}
});
var CheckoutButton = React.createClass({
render: function() {
return (
<div className="CheckoutButton">
<button>Book securely</button>
<span><i className="fa fa-fw fa-lock"></i> Your credit card information is encrypted</span>
</div>
);
}
});
var ExpiryDate = React.createClass({
render: function() {
return (
<div className="ExpiryDate">
<div>
<label>Expires on</label>
<div className="Expiry">
<select>
<option value="">January</option>
<option value="">February</option>
<option value="">March</option>
<option value="">April</option>
<option value="">May</option>
<option value="">June</option>
<option value="">July</option>
<option value="">August</option>
<option value="">September</option>
<option value="">October</option>
<option value="">November</option>
<option value="">December</option>
</select>
<select name="" id="">
<option value="">2016</option>
<option value="">2017</option>
<option value="">2018</option>
<option value="">2019</option>
<option value="">2020</option>
<option value="">2021</option>
</select>
</div>
</div>
<div className="CVCField">
<label>CVC</label>
<input placeholder="000" type="number" />
</div>
</div>
);
}
});
var BasicInput = React.createClass({
render: function() {
return (
<div className="BasicInput">
<label for={this.props.name}>{this.props.label}</label>
<input id={this.props.name} type={this.props.type} placeholder={this.props.placeholder} />
</div>
);
}
});
var Checkout = React.createClass({
render: function() {
return (
<div className="Checkout">
<OrderSummary discount={this.props.discount} tax={this.props.tax} price={this.props.price} duration={this.props.duration} />
<PaymentForm onSubmit={this.props.onSubmit} />
</div>
);
}
});
var Header = React.createClass({
render: function() {
return (
<header>
<input onChange={this.props.onChange} type="range" max="100" min="1" step="1" />
</header>
);
}
});
var App = React.createClass({
getInitialState: function() {
return ({
mounted: false,
people: 1,
price: 320.00,
tax: 20,
duration: 5,
discount: 5
});
},
componentDidMount: function() {
this.setState({ mounted: true });
},
handleSubmit: function(e) {
e.preventDefault();
},
handleChange: function(e) {
console.log(e.target.value);
this.setState({ duration: e.target.value });
},
render: function() {
var overlay, container;
if(this.state.mounted) {
overlay = (
<Overlay image="https://s3-us-west-2.amazonaws.com/s.cdpn.io/557257/jj-2.jpg" />
);
container = (
<Container>
<ImagePreview price={this.state.price} duration={this.state.duration} people={this.state.people} image="https://s3-us-west-2.amazonaws.com/s.cdpn.io/557257/jj-2.jpg" />
<Checkout discount={this.state.discount} tax={this.state.tax} price={this.state.price} duration={this.state.duration} onSubmit={this.handleSubmit} />
</Container>
);
}
return(
<div className="App">
<ReactCSSTransitionGroup transitionName="overlay" transitionEnterTimeout={500} transitionLeaveTimeout={300}>
{overlay}
</ReactCSSTransitionGroup>
<ReactCSSTransitionGroup transitionName="container" transitionEnterTimeout={500} transitionLeaveTimeout={300}>
{container}
</ReactCSSTransitionGroup>
<Header onChange={this.handleChange} />
</div>
);
}
});
ReactDOM.render(
<App />,
document.getElementById('app')
);
View Compiled