When I was building my personal webpage, I figured out that I need to build component to show my recent work. Browsing through internet I found a lot of useful example but usualy based on stateful components which was approach I tried to avoid. Today I show you how to build one for your own use.
You can find working example of this component here & check the repository
Sourcing the content
In this example we will source data from JSON file. You can find the plugin with great documentation here.
In my personal portofilo I'm sourcing content from Netlify CMS (docs here). It depends on your project from where you source your data but the approach should be similar.
plugins: [
`gatsby-transformer-json`,
{
resolve: `gatsby-source-filesystem`,
options: {
path: `./src/data/`,
},
},
]
As you can see in the gatsby-config.js
example above. I put my json data to /src/data/
folder.
Adding content
In data.json
we make object with all our portfolio items.
I added four attributes to each item:
- Name
- Image
- Technolgies object
- Link to website
[
{
"name": "Portfolio item 1",
"img": "img/1.jpg",
"link": "https://www.pexels.com/photo/mountain-surrounded-with-fog-1772973/",
"technologies": [
"React",
"GatsbyJS"
]
},
{
"name": "Portfolio item 2",
"img": "img/2.jpg",
"link": "https://www.pexels.com/photo/aerial-photography-of-mountain-1321059/",
"technologies": [
"React",
"GatsbyJS",
"Netlify CMS"
]
}
...
...
}
Don't forget to put images to subfolder next to you data.json
. In this example it is /src/data/img/
Making the component
First of all we need to source our data from GraphQl. We can find our data in allDataJson
query. Gatsby Transformer Sharp plugin will make from our image path string object so we can use it with Gatsby Image plugin.
We also need to import useStaticQuery, graphql and gatsby-image plugins
.
import React from 'react'
import { useStaticQuery, graphql } from "gatsby"
import Img from "gatsby-image"
export default () => {
const data = useStaticQuery(graphql`
query HeaderQuery {
allDataJson {
edges {
node {
name
img {
childImageSharp {
fluid(maxWidth: 4000) {
...GatsbyImageSharpFluid_noBase64
}
}
}
link
technologies
}
}
}
}
`)
//Portfolio items
const items = data.allDataJson.edges;
return (
)
}
Ok, let's add some content. We will use map function to loop through all portfolio items.
return (
<section className="portfolio">
// Loop through portfolio items
{items.map(({ node }, index) =>
<div
key={index}
className="portfolio__item">
// Check if the image object exists
{node.img &&
<div className="item__image">
// Using gatsby-image plugin
<Img
fluid={node.img.childImageSharp.fluid}
alt={`${node.name} image`} />
</div>
}
<h2>{node.name}</h2>
// Check if the technolgies object exists
{node.technologies &&
<div className="item__technologies">
// Loop through technologies object
{node.technologies.map((technology, index) =>
<p key={index}>{technology}</p>
)}
</div>
}
// Check if link exists
{node.link &&
<a href={node.link}>Open website</a>
}
</div>
)}
</section>
)
No let's make same State Hooks. We will need three all together:
// Boolean state for modal opening/closing
const [modal, setModal] = React.useState(false)
// State for storing selected portfolio index
const [currentIndex, setCurrentIndex] = React.useState(0)
// State storing side to which animation of slider should go
const [slideSide, setSlideSide] = React.useState(null)
Now lets add function which opens modal after click on portfolio item.
As you can see below we set the modal state to true and store portfolio item index.
const onClick = index => {
setModal(true)
setCurrentIndex(index)
}
Next we will make previous and next function to get through slides in modal.
const previous = () => {
// Set slide animation to left
setSlideSide("left")
// If first item or not
currentIndex > 0
? setCurrentIndex(currentIndex - 1)
: setCurrentIndex(items.length - 1)
}
const next = () => {
// Set slide animation to right
setSlideSide("right")
// If last item or not
currentIndex < items.length - 1
? setCurrentIndex(currentIndex + 1)
: setCurrentIndex(0)
}
Lets build the modal now. We need to check if modal is set to true first.
// Check if modal state === true
modal &&
<div
className={`portfolio__modal`}>
// Clickable overlay to close modal
<div
className="modal__overlay"
onClick={() => setModal(false)} />
// Previous item button
<button
onClick={() => previous()}
className="modal__previous"/>
// slideSide state and accordingly class changes depending
// on which button clicked
// onAnimationEnd slideSide state is set back to null
<div
className={`modal__content modal__content--slide-{slideSide}`}
onAnimationEnd={() => setSlideSide(null)}>
// Modal closing button
<button
onClick={() => setModal(!modal)}
className="modal__close"/>
// Active item image
{activeSlide.img &&
<div className="item__image">
<Img
fluid={activeSlide.img.childImageSharp.fluid}
alt={`${activeSlide.name} image`} />
</div>
}
// Active item name
<h2>{activeSlide.name}</h2>
// Active item technologies
{activeSlide.technologies &&
<div className="item__technologies">
{activeSlide.technologies.map((technology, index) =>
<p key={index}>{technology}</p>
)}
</div>
}
// Active item link
{activeSlide.link &&
<a href={activeSlide.link}>Open website</a>
}
</div>
// Next item button
<button
onClick={() => next()}
className="modal__next"/>
</div>
I put here example of left right slider animation also.
.portfolio__modal .modal__content--slide-left {
animation: slideLeft 0.2s ease-out;
}
.portfolio__modal .modal__content--slide-right {
animation: slideRight 0.2s ease-out;
}
@keyframes slideRight {
0% {
transform: translateX(5%) translateY(-50%);
opacity: 0.2;
}
100% {
transform: translateX(0) translateY(-50%);
opacity: 1;
}
}
@keyframes slideLeft {
0% {
transform: translateX(-5%) translateY(-50%);
opacity: 0.2;
}
100% {
transform: translateX(0) translateY(-50%);
opacity: 1;
}
}
And when we put it all together we have very basic example of portfolio component. I hope that I made it at least slightly understendable and you find useful.
p.s.: apologize my gramatics, I did my best but english is not my native language
You can find repository here and live example here