#app
View Compiled
// Import fonts
@import 'https://fonts.googleapis.com/css?family=Oswald:300|Slabo+27px';
// Sassy stuff
$white: #ffffff;
$black: #333333;
$red: #e74c3c;
$transblack: rgba(66,66,66,0.7);
@mixin header-font($size) {
font: {
family:'Oswald';
size: $size;
weight: 300;
}
text-transform: uppercase;
letter-spacing: 0.1em;
}
// Base Stuff inc responsive text
body, html, #app {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
:root {
font: {
family: 'Slabo 27px';
size: 14px;
}
color: $black;
}
// Tablet
@media screen and (min-width:768px){
:root { font-size: 16px; }
}
// Desktop
@media screen and (min-width:1024px){
:root { font-size: 18px; }
}
// Large Desktop
@media screen and (min-width:1440px){
:root { font-size: 23px; }
}
// Main Styles
.gallery {
display: flex;
flex-flow: row wrap;
justify-content: center;
padding: 34px;
}
.gallery-tile {
min-width: 200px;
max-width: 28vw;
margin: 0.25em;
overflow: hidden;
position: relative;
cursor: pointer;
img {
width: 100%;
height: 100%;
transition: transform 300ms ease-in-out, filter 300ms ease-in-out;
}
.picture-info {
& > * {
margin: 0.5rem 1.25rem;
}
h2 {
@include header-font(2.5rem);
}
position: absolute;
z-index: 1;
color: $white;
width: 100%;
height: 100%;
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: flex-start;
opacity: 0;
transition: opacity 300ms ease-in-out,
filter 300ms ease-in-out;
}
&:hover {
img {
transform: scale(1.1);
filter: brightness(80%);
}
.picture-info {
opacity: 1;
}
}
}
.imageview-wrapper {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.imageview {
display: flex;
justify-content: center;
}
.imageview > * { margin: 3em; }
.imageview-image {
width: 25em;
height: 25em;
box-shadow: 0 20px 40px -5px $transblack;
}
.imageview-info {
max-width: 260px;
text-align: right;
position: relative;
button {
@include header-font(1.5rem);
color: $red;
cursor: pointer;
border: none;
background: none;
outline: none;
margin: 0;
padding: 0;
position: absolute;
top: -2em;
right: -1em;
&:hover {
animation: swell 300ms ease-in-out;
}
}
h2 {
@include header-font(2.5rem);
margin-top: 0;
}
h3 {
@include header-font(1.5rem);
}
ul {
list-style: none;
}
}
.fadeIn { animation: fade 300ms ease-in-out forwards; }
@keyframes fade {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes swell {
0% { transform: scale(1); }
50% { transform: scale(1.3); }
100% { transform: scale(1); }
}
View Compiled
class App extends React.Component {
constructor() {
super()
this.state = {
data: [],
activeID: 0,
imageView: false
}
}
componentWillMount() {
this._loadData('https://s3-us-west-2.amazonaws.com/s.cdpn.io/735173/rpg-2-data.json')
}
componentWillUnmount() {
this._loadData.abort()
}
// Fetch data, then clone it to state using destructuring
// XHR Fallback
_loadData(url) {
fetch(url, {
method: 'GET'
})
.then(response => response.json())
.then(json => this.setState({
data: [...json.gallery]
}))
.catch((err) => {
console.log(err.message)
try {
const xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload = () => {
let json = xhr.response
this.setState({
data: [...json.gallery]
})
}
xhr.onerror = () => {
throw new Error('XMLHttpRequest Failed...')
}
xhr.send()
} catch (e) {
console.log(e.message)
}
})
}
_openImageView(id) {
this.setState({
activeID: id,
imageView: true
});
}
_closeImageView() {
this.setState({
imageView: false
})
}
render() {
return (
<div className="wrapper">
{
this.state.imageView ?
<ImageView {...this.state.data[this.state.activeID]}
_closeImageView={this._closeImageView.bind(this)} />
:
<Gallery data={this.state.data}
_openImageView={this._openImageView.bind(this)} />
}
</div>
)
}
}
class ImageView extends React.Component {
render() {
return (
<div className="imageview-wrapper fadeIn">
<div className="imageview">
<Image CSSClass="imageview-image"
src={this.props.src}
alt={this.props.name} />
<div className="imageview-info">
<button className="imageview-close" onClick={this.props._closeImageView}>x</button>
<h2>{this.props.name}</h2>
<p>{this.props.desc}</p>
<h3>Tags</h3>
<ul>
{this.props.tags.map(tag => <li>{tag}</li>)}
</ul>
</div>
</div>
</div>
)
}
}
class Gallery extends React.Component {
render() {
return (
<div className="gallery fadeIn">
{
this.props.data.map( data =>
<Tile key={data.id}
id={data.id}
src={data.src}
name={data.name}
desc={data.desc}
_openImageView={this.props._openImageView} />
)
}
</div>
)
}
}
class Tile extends React.Component {
_handleClick() {
this.props._openImageView(this.props.id)
}
render() {
return (
<div className="gallery-tile" onClick={this._handleClick.bind(this)}>
<div className="picture-info">
<h2>{this.props.name}</h2>
{/*<p>{this.props.desc}</p>*/}
</div>
<Image
CSSClass="tile-image"
src={this.props.src}
alt={this.props.name} />
</div>
)
}
}
const Image = (props) => (
<img
className={props.CSSClass}
src={props.src}
alt={props.name} />
)
// Render app
ReactDOM.render(<App />, document.getElementById('app'))
View Compiled
This Pen doesn't use any external CSS resources.