$background: #F1F1F1;
$header-color: #666666;
html, body {
background: $background;
font-family: roboto;
}
* {
font-family: roboto;
font-weight: 300;
}
// @keyframes flipBack {
// 0% {
// transform: rotateY(180deg);
// }
// 100% {
// transform: rotateY(0deg);
// }
// }
* {
position: relative;
}
.wrapper {
width: 100%;
height: 100vh;
}
.content-wrapper {
display: flex;
height: 93vh;
justify-content: center;
align-items: center;
background: $background;
margin: 0 auto;
padding: 0 2em;
}
.card {
width: 30em;
height: 15em;
display: flex;
justify-content: center;
align-items: center;
background: white;
box-shadow: 2px 3px 23px rgba(0,0,0,.1);
padding: 1em 3em;
transform-style: preserve-3d;
margin-top: 4em;
transition: 0.2s;
&.back {
}
@media(max-width: 600px) {
width: 25em;
}
@media(max-height: 823px) {
margin-top: 9em;
}
}
.card__flip-card {
position: absolute;
top: 4%;
right: 5%;
cursor: pointer;
color: #333;
}
.card__counter {
position: absolute;
left: 5%;
top: 5%;
}
.card__content--front {
animation: fadeInUp 0.5s;
font-weight: bold;
}
.card__content--back {
animation: fadeIn 0.6s;
line-height: 1.5;
font-size: 14px;
}
.header {
background: white;
box-shadow: 7px 7px 22px lightgray;
padding: 1em 0em;
display: flex;
align-items: center;
height: 3vh;
color: $header-color;
position: absolute;
width: 100%;
z-index: 1;
}
.header-content {
width: 33.33%;
display: flex;
justify-content: center;
font-weight: 400;
span {
font-size: 1.3em;
cursor: pointer
}
}
.header-content__right {
justify-content: flex-end;
z-index: 11;
}
.header-content__left {
justify-content: flex-start;
span {
margin-left: 1em;
}
}
//create-card
.create-card {
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: 0;
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
}
.create-card__shadow {
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: 0;
background: rgba(0,0,0,0.5);
z-index: 101;
height: 100%;
width: 100%;
transition: 0.5s;
}
.create-card__body {
position: absolute;
height: 20em;
width: 30em;
z-index: 1000;
background: rgba(255,255,255,0.95);
animation: fadeIn 0.5s;
text-align: center;
box-shadow: inset 0px 0px 0px 3px gray;
outline: 4px solid white;
left: 50%;
top: 50%;
transform: translateX(-50%)translateY(-50%);
h1 {
font-size: 1.3em;
margin-top: 2.5em;
color: #333;
}
}
.create-card__placeholder {
transition: 0.2s;
}
.create-card__input-wrapper {
text-align: center;
input {
margin: 0.75em 0;
padding: 0.5em 1em;
width: 70%;
}
}
#create-card__button {
border: 1px solid;
background: white;
color: #333;
cursor: pointer;
margin-top: 1.75em;
padding: 0.5em 0.75em;
&:focus {
outline: none;
}
}
.create-card__error {
color: tomato;
margin-top: 1em;
}
.card-container__icon {
position: fixed;
z-index: 100;
font-size: 1.5em;
top: 0.5em;
left: 94%;
transform: translateX(-50%);
cursor: pointer;
color: #333;
}
.card__actions {
position: absolute;
bottom: 10%;
display: flex;
width: 100%;
justify-content: center;
opacity: 0;
transition: 0.3s;
left: 0;
&.active {
opacity: 1;
}
}
.card__next-button {
margin-left: 0.5em;
cursor: pointer;
padding: 0.5em 0.75em;
background: $background;
color: #444;
transition: 0.2s;
&:hover {
background: darken($background, 10%);
}
}
.card__prev-button {
margin-right: 0.5em;
cursor: pointer;
padding: 0.5em 0.75em;
background: $background;
color: #444;
transition: 0.2s;
&:hover {
background: darken($background, 10%);
}
}
.card-container__dot {
margin: 0 1em;
}
.card-container__dots-wrapper {
padding: 1em 0em;
margin-top: 1em;
text-align: center;
}
.card-container__dot {
color: lightgray;
cursor: pointer;
&.active {
color: dodgerblue;
transition: 0.5s;
}
}
View Compiled
class CreateCard extends React.Component {
constructor() {
super();
this.state = {
word: '',
description: '',
showError: false
}
}
hideError() {
this.setState({showError: !this.state.showError});
}
render() {
const errorMessage = this.state.showError ? 'Please fill in the word and description!' : '';
return (
<div className='create-card'>
<div
className='create-card__shadow'
onClick={() => {
this.props.onShadowClick();
}}
>
</div>
<div className='create-card__body'>
<h1>Create New Card</h1>
<div className='create-card__input-wrapper'>
<input
id='word'
placeholder="Word i.e. 'React'"
value = {this.state.word}
onChange = {(e) => this.setState({word: e.target.value})}
/>
<input
id='description'
placeholder="Description i.e. 'A front end js framework.'"
value = {this.state.description}
onChange = {(e) => this.setState({description: e.target.value})}
/>
<br/>
<button
id='create-card__button'
onClick={() => {
if (this.state.word.length === 0 || this.state.description.length === 0) {
this.setState({showError: !this.state.showError});
setTimeout(() => this.hideError(), 2000);
} else {
this.props.onShadowClick();
const word = new Immutable.Map({word: this.state.word, description: this.state.description});
this.props.onCreateCard(word);
}
}}
>
Create!
</button>
<div className='create-card__error'>
{errorMessage}
</div>
</div>
</div>
</div>
);
}
}
class Header extends React.Component {
constructor() {
super();
this.state = {
showModal: false
}
}
render() {
return (
<div className='header'>
<div className='header-content header-content__left'>
</div>
<div className='header-content header-content__middle'>
Flash Cards
</div>
<div className='header-content header-content__right'>
</div>
</div>
)
}
}
class Card extends React.Component {
constructor() {
super();
this.state = {
showAnswer: false
}
}
render() {
const content = this.state.showAnswer ? this.props.backContent : this.props.frontContent;
const iconClass = this.state.showAnswer ? 'reply' : 'share';
const cardClass = this.state.showAnswer ? 'back' : '';
const contentClass = this.state.showAnswer ? 'back' : 'front';
const actionClass = this.state.showAnswer ? 'active' : '';
return (
<div
className={`card ${cardClass}`}
onClick={() => this.setState({showAnswer: !this.state.showAnswer})}
>
<span className='card__counter'>{this.props.cardNumber + 1}</span>
<div
className='card__flip-card'
onClick={ () => {
this.setState({showAnswer: !this.state.showAnswer});
}}
>
<span className={`fa fa-${iconClass}`}/>
</div>
<div className={`card__content--${contentClass}`}>
{content}
</div>
<div className={`card__actions ${actionClass}`}>
<div
className='card__prev-button'
onClick={() => {
this.props.showPrevCard();
this.setState({showAnswer: false});
}}
>
Prev
</div>
<div
className='card__next-button'
onClick={() => {
this.props.showNextCard();
this.setState({showAnswer: false});
}}
>
Next
</div>
</div>
</div>
);
}
}
class CardContainer extends React.Component {
constructor() {
super();
this.state = {
cards: Immutable.fromJS([{
word: 'Jazz',
description: 'A type of music of black American origin characterized by improvisation, syncopation, and usually a regular or forceful rhythm, emerging at the beginning of the 20th century.',
}, {
word: 'Reggae',
description: 'Music like Bob Marley, man.',
}, {
word: 'Folk',
description: 'Music like Bob Dylan, man.',
}
]),
cardNumber: 0
};
this.boundCallback = this.hideCreateCard.bind(this);
this.boundCreateCard = this.setCard.bind(this);
this.boundShowPrevCard = this.showPrevCard.bind(this);
this.boundShowNextCard = this.showNextCard.bind(this);
}
hideCreateCard() {
this.setState({showModal: false});
}
showNextCard() {
if ((this.state.cardNumber + 1) !== this.state.cards.size) {
this.setState({cardNumber: this.state.cardNumber += 1});
}
}
showPrevCard() {
if (this.state.cardNumber !== 0) {
this.setState({cardNumber: this.state.cardNumber -= 1});
}
}
setCard(card) {
const newCards = this.state.cards.push(card);
this.setState({cards: newCards});
}
generateDots() {
const times = this.state.cards.size;
let arr = [];
_.times(times).forEach((num) => {
const dotClass = num === this.state.cardNumber ? 'active' : '';
arr.push(
<span
className={`card-container__dot fa fa-circle ${dotClass}`}
onClick={() => this.setState({cardNumber: num})}
/>
)
});
return arr;
}
generateCards() {
const cards = this.state.cards;
const cardsList = cards.map((card) => {
return (
<Card
frontContent={card.get('word')}
backContent={card.get('description')}
showNextCard={this.boundShowNextCard}
showPrevCard = {this.boundShowPrevCard}
cardNumber={this.state.cardNumber}
/>
);
})
return(cardsList.toJS()[this.state.cardNumber]);
}
render() {
return (
<div>
<span
className='card-container__icon fa fa-plus'
onClick={() => {
this.setState({showModal: !this.state.showModal});
}}
/>
{this.state.showModal
? <CreateCard
onShadowClick={this.boundCallback}
onCreateCard={this.boundCreateCard}
/>
: ''}
{this.generateCards()}
<div className='card-container__dots-wrapper'>
{this.generateDots()}
</div>
</div>
);
}
}
class Main extends React.Component {
render() {
return (
<div className='wrapper'>
<Header />
<div className='content-wrapper'>
<CardContainer />
</div>
</div>
);
}
}
ReactDOM.render(<Main />, document.getElementById('app'));
View Compiled