Skip to content

Instantly share code, notes, and snippets.

@avwave
Created September 3, 2018 10:15
Show Gist options
  • Save avwave/523334732df17cb299f610cfec5bef95 to your computer and use it in GitHub Desktop.
Save avwave/523334732df17cb299f610cfec5bef95 to your computer and use it in GitHub Desktop.
React - Inbox
div.wrapper
div#inbox
div.footer.
Built with React and powered by ES6.
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
/* App */
class App extends React.Component {
constructor(args) {
super(args);
// Assign unique IDs to the emails
const emails = this.props.emails;
let id = 0;
for (const email of emails) {
email.id = id++;
}
this.state = {
selectedEmailId: 0,
currentSection: 'inbox',
emails
};
}
openEmail(id) {
const emails = this.state.emails;
const index = emails.findIndex(x => x.id === id);
emails[index].read = 'true';
this.setState({
selectedEmailId: id,
emails
});
}
deleteMessage(id) {
// Mark the message as 'deleted'
const emails = this.state.emails;
const index = emails.findIndex(x => x.id === id);
emails[index].tag = 'deleted';
// Select the next message in the list
let selectedEmailId = '';
for (const email of emails) {
if (email.tag === this.state.currentSection) {
selectedEmailId = email.id;
break;
}
}
this.setState({
emails,
selectedEmailId
});
}
setSidebarSection(section) {
let selectedEmailId = this.state.selectedEmailId;
if (section !== this.state.currentSection) {
selectedEmailId = '';
}
this.setState({
currentSection: section,
selectedEmailId
});
}
render() {
const currentEmail = this.state.emails.find(x => x.id === this.state.selectedEmailId);
return (
<div>
<Sidebar
emails={this.props.emails}
setSidebarSection={(section) => { this.setSidebarSection(section); }} />
<div className="inbox-container">
<EmailList
emails={this.state.emails.filter(x => x.tag === this.state.currentSection)}
onEmailSelected={(id) => { this.openEmail(id); }}
selectedEmailId={this.state.selectedEmailId}
currentSection={this.state.currentSection} />
<EmailDetails
email={currentEmail}
onDelete={(id) => { this.deleteMessage(id); }} />
</div>
</div>
)
}
}
/* Sidebar */
const Sidebar = ({ emails, setSidebarSection }) => {
var unreadCount = emails.reduce(
function(previous, msg) {
if (msg.read !== "true" ) {
return previous + 1;
}
else {
return previous;
}
}.bind(this), 0);
var deletedCount = emails.reduce(
function(previous, msg) {
if (msg.tag === "deleted") {
return previous + 1;
}
else {
return previous;
}
}.bind(this), 0);
return (
<div id="sidebar">
<div className="sidebar__compose">
<a href="#" className="btn compose">
Compose <span className="fa fa-pencil"></span>
</a>
</div>
<ul className="sidebar__inboxes">
<li onClick={() => { setSidebarSection('inbox'); }}><a>
<span className="fa fa-inbox"></span> Inbox
<span className="item-count">{unreadCount}</span></a></li>
<li onClick={() => { setSidebarSection('sent'); }}><a>
<span className="fa fa-paper-plane"></span> Sent
<span className="item-count">0</span></a></li>
<li onClick={() => { setSidebarSection('drafts'); }}><a>
<span className="fa fa-pencil-square-o"></span> Drafts
<span className="item-count">0</span>
</a></li>
<li onClick={() => { setSidebarSection('deleted'); }}><a>
<span className="fa fa-trash-o"></span> Trash
<span className="item-count">{deletedCount}</span>
</a></li>
</ul>
</div>
);
};
/* Email classes */
const EmailListItem = ({ email, onEmailClicked, selected }) => {
let classes = "email-item";
if (selected) {
classes += " selected"
}
return (
<div onClick={() => { onEmailClicked(email.id); }} className={classes}>
<div className="email-item__unread-dot" data-read={email.read}></div>
<div className="email-item__subject truncate">{email.subject}</div>
<div className="email-item__details">
<span className="email-item__from truncate">{email.from}</span>
<span className="email-item__time truncate">{getPrettyDate(email.time)}</span>
</div>
</div>
);
}
const EmailDetails = ({ email, onDelete }) => {
if (!email) {
return (
<div className="email-content empty"></div>
);
}
const date = `${getPrettyDate(email.time)} · ${getPrettyTime(email.time)}`;
const getDeleteButton = () => {
if (email.tag !== 'deleted') {
return <span onClick={() => { onDelete(email.id); }} className="delete-btn fa fa-trash-o"></span>;
}
return undefined;
}
return (
<div className="email-content">
<div className="email-content__header">
<h3 className="email-content__subject">{email.subject}</h3>
{getDeleteButton()}
<div className="email-content__time">{date}</div>
<div className="email-content__from">{email.from}</div>
</div>
<div className="email-content__message">{email.message}</div>
</div>
);
};
/* EmailList contains a list of Email components */
const EmailList = ({ emails, onEmailSelected, selectedEmailId }) => {
if (emails.length === 0) {
return (
<div className="email-list empty">
Nothing to see here, great job!
</div>
);
}
return (
<div className="email-list">
{
emails.map(email => {
return (
<EmailListItem
onEmailClicked={(id) => { onEmailSelected(id); }}
email={email}
selected={selectedEmailId === email.id} />
);
})
}
</div>
);
};
// Render
$.ajax({url: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/311743/dummy-emails.json',
type: 'GET',
success: function(result) {
React.render(<App emails={result} />, document.getElementById('inbox'));
}
});
// Helper methods
const getPrettyDate = (date) => {
date = date.split(' ')[0];
const newDate = date.split('-');
const month = months[0];
return `${month} ${newDate[2]}, ${newDate[0]}`;
}
// Remove the seconds from the time
const getPrettyTime = (date) => {
const time = date.split(' ')[1].split(':');
return `${time[0]}:${time[1]}`;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react.min.js"></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
@import "compass";
// Fonts
@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,300,600);
$font-base: 'Open Sans', Arial, sans-serif;
$blue-1: #009fc4;
$blue-2: #0c7ead;
*, *:before, *:after {
box-sizing: border-box;
}
body {
background: #eee;
font-family: $font-base;
font-size: 14px;
line-height: 150%;
}
.truncate {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
.disclaimer {
margin: 0 auto 50px;;
text-align: center;
width: 400px;
}
// Buttons
.btn {
border: none;
cursor: pointer;
display: block;
font-family: $font-base;
font-size: 16px;
outline: none;
padding: 15px;
transition: all .1s linear;
}
// Inbox wrapper
$app-height: 500px;
$sidebar-width: 200px;
.wrapper {
box-shadow: 0 4px 20px rgba(51, 51, 51, .2);
margin: 50px auto;
overflow: auto;
width: 1024px;
.inbox-container {
float: left;
height: $app-height;
width: calc(100% - #{$sidebar-width});
}
}
#sidebar {
background: #34393d;
float: left;
height: $app-height;
width: $sidebar-width;
.sidebar__inboxes {
margin-top: 50px;
.item-count {
background: #34393d;
border-radius: 5px;
float: right;
padding: 2px 8px;
margin: -2px -8px;
}
li a {
color: #fff;
cursor: pointer;
display: block;
margin-bottom: 10px;
padding: 15px;
text-decoration: none;
transition: background .1s linear;
width: 100%;
&:hover {
background: #404549;
}
}
.fa {
margin-right: 10px;
}
}
.btn.compose {
color: #fff;
padding: 30px 15px;
text-align: center;
text-decoration: none;
transition: all .1s linear;
width: 100%;
@include background-image(linear-gradient(bottom right, $blue-1, $blue-2));
&:hover {
background-size: 150%;
.fa {
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
}
.fa {
margin-left: 10px;
transition: all .1s linear;
}
}
}
// Email list
.email-list {
background: #f5f5f5;
float: left;
height: $app-height;
max-height: 100%;
overflow-y: auto;
width: 35%;
&.empty {
color: #aaa;
padding-top: 200px;
text-align: center;
}
.email-item {
background: #fff;
border-bottom: 1px solid #eee;
cursor: pointer;
padding: 10px 15px;
position: relative;
&.selected {
color: $blue-1;
}
&__subject {
margin-bottom: 8px;
}
&__details {
font-size: 12px;
opacity: .5;
text-transform: uppercase;
}
&__unread-dot {
height: 100%;
right: 10px;
position: absolute;
top: 10px;
&[data-read="false"]:before {
background: $blue-1;
border-radius: 50%;
content: '';
display: block;
height: 6px;
width: 6px;
}
}
&__time {
float: right;
text-align: right;
width: 40%;
}
&__from.truncate {
width: 60%;
}
&:hover:not(.selected) {
background: #fafafa;
}
}
}
// Email content
.email-content {
background: #fff;
border-left: 1px solid #ddd;
float: left;
height: $app-height;
position: relative;
width: 65%;
&__header {
background: #f5f5f5;
border-bottom: 1px solid #eee;
padding: 10px 15px;
}
&__subject {
font-size: 18px;
margin: 10px 0;
}
&__details {
font-size: 12px;
opacity: .5;
text-transform: uppercase;
}
&__time {
color: #878787;
float: right;
}
&__from {
color: #878787;
}
&__message {
padding: 20px 15px 15px;
}
.delete-btn {
cursor: pointer;
margin: -5px;
padding: 5px;
position: absolute;
right: 20px;
top: 24px;
transition: color .1s linear;
&:hover {
color: #E23E57;
}
}
}
// Footer
.footer {
background: #f5f5f5;
border-top: 1px solid #ddd;
color: #999;
clear: both;
font-size: 14px;
padding: 10px;
text-align: center;
width: 100%;
}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment