Skip to content

Commit 0e95720

Browse files
Sergiy Dybskiym-allanson
authored andcommitted
feat(blog): A tutorial on how to create a lightbox using gatsby-image and @reach/dialog (#9684)
<!-- Q. Which branch should I use for my pull request? A. Use `master` branch (probably). Q. Which branch if my change is a bug fix for Gatsby v1? A. In this case, you should use the `v1` branch Q. Which branch if I'm still not sure? A. Use `master` branch. Ask in the PR if you're not sure and a Gatsby maintainer will be happy to help :) Note: We will only accept bug fixes for Gatsby v1. New features should be added to Gatsby v2. Learn more about contributing: https://www.gatsbyjs.org/docs/how-to-contribute/ --> closes #7005
1 parent 3739479 commit 0e95720

File tree

7 files changed

+352
-0
lines changed

7 files changed

+352
-0
lines changed
Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
---
2+
title: "Building a custom, accessible image lightbox"
3+
date: 2018-12-20
4+
author: "Sergiy Dybskiy"
5+
tags: ["lightbox", "gatsby-image", "accessibility"]
6+
canonicalLink: "https://416serg.me/building-a-custom-accessible-image-lightbox-in-gatsbyjs"
7+
---
8+
9+
In this tutorial you're going to cover the steps to creating a simple, custom, accessible image lightbox inside a [GatsbyJS](/) application. You can check out the finished example on [Github](https://github.com/416serg/GatsbyLightbox) ([Demo](https://gatsbylightboxv2.416serg.me/)) or continue reading to dive right into the magic.
10+
11+
## Getting started
12+
13+
If you don't have Gatsby installed, open up your terminal and type in:
14+
15+
```terminal
16+
$ npm install --global gatsby-cli
17+
```
18+
19+
Then, still in your Terminal, head over to a folder you'd want to get started in and type in:
20+
21+
```terminal
22+
$ gatsby new GatsbyLightbox https://github.com/gatsbyjs/gatsby-starter-default
23+
```
24+
25+
Once it's done scaffolding the starter application, type in:
26+
27+
```terminal
28+
$ cd GatsbyLightbox
29+
$ yarn develop
30+
```
31+
32+
Now open up your browser to `localhost:8000` and you should see the starter application ready to go.
33+
34+
![Gatsby Starter Screenshot](/screen-4.png)
35+
36+
Open up your favorite code editor to start setting stuff up.
37+
38+
## Gatsby Image
39+
40+
Dealing with images on the web has always been an issue. You have to have the right sizes, you have to have the right formats, if something is too big, it will load slowly, etc. Luckily, Gatsby built a plugin to handle images elegantly called [`gatsby-image`](https://www.npmjs.com/package/gatsby-image). Using Gatsby's GraphQL queries, you can request different sizes, formats and have a few options as to how you want to handle the image load. For this tutorial you'll be using a blur up approach, similar to how Medium handles their images.
41+
42+
If you open up the `index.js` file in the pages folder, you'll see there is an `<Image/>` component that does exactly that.
43+
44+
You'll take a similar approach.
45+
46+
### Configure `gatsby-config.js`
47+
48+
You'll put all of the images in a folder inside `src/cars` (You can get them from the [Github repo](https://github.com/416serg/GatsbyLightbox/tree/master/src/cars) or use your own, just make sure to follow a similar format). Then, you'll edit the `gatsby-config.js` file to expose that folder to a GraphQL query.
49+
50+
```js
51+
{
52+
resolve: `gatsby-source-filesystem`,
53+
options: {
54+
name: `cars`,
55+
path: `${__dirname}/src/cars`,
56+
},
57+
},
58+
```
59+
60+
Add that under the existing filesystem config and then restart your Gatsby server (I believe it's necessary for it to build the query before calling it).
61+
62+
### Creating a `Cars` component
63+
64+
Now that you have configured the filesystem, go ahead and create a component that will request all the images with a GraphQL query.
65+
66+
Create a new file inside `src/components` called `cars.js`.
67+
68+
```js
69+
import React from "react"
70+
import { StaticQuery, graphql } from "gatsby"
71+
import Lightbox from "./lightbox"
72+
73+
const Cars = () => (
74+
<StaticQuery
75+
query={graphql`
76+
query {
77+
carImages: allFile(filter: { sourceInstanceName: { eq: "cars" } }) {
78+
edges {
79+
node {
80+
childImageSharp {
81+
fluid(maxWidth: 2000) {
82+
...GatsbyImageSharpFluid
83+
}
84+
}
85+
}
86+
}
87+
}
88+
}
89+
`}
90+
render={data => <Lightbox carImages={data.carImages.edges} />}
91+
/>
92+
)
93+
export default Cars
94+
```
95+
96+
Notice how the `StaticQuery` component is using a render prop where it's returning your `Lightbox` component. For now, it's a simple component inside `components/lightbox.js` that looks like this:
97+
98+
```js
99+
import React, { Component } from "react"
100+
import PropTypes from "prop-types"
101+
import Img from "gatsby-image"
102+
103+
export default class Lightbox extends Component {
104+
static propTypes = {
105+
carImages: PropTypes.array.isRequired,
106+
}
107+
108+
render() {
109+
const { carImages } = this.props
110+
return (
111+
<div>
112+
{carImages.map(image => (
113+
<Img
114+
key={image.node.childImageSharp.fluid.src}
115+
fluid={image.node.childImageSharp.fluid}
116+
/>
117+
))}
118+
</div>
119+
)
120+
}
121+
}
122+
```
123+
124+
By now, you should have all the images displaying on the home page and doing a fade-up load.
125+
126+
### Styled Components
127+
128+
For styling, you're going to be using [styled-components](https://www.styled-components.com/). To get it configured with Gatsby, run the following inside your terminal in your application:
129+
130+
```terminal
131+
$ yarn add gatsby-plugin-styled-components styled-components babel-plugin-styled-components
132+
```
133+
134+
And add the following to `gatsby-config.js`:
135+
136+
```js
137+
module.exports = {
138+
/* existing config */
139+
plugins: [
140+
`gatsby-plugin-styled-components`,
141+
/* existing plugins */
142+
],
143+
}
144+
```
145+
146+
Lets restart the application to make sure the config has taken its effect. Now, you're going to create a simple `LightboxContainer` styled component to wrap your images in. Your `lightbox.js` component should look like this now:
147+
148+
```js
149+
import React, { Component } from "react"
150+
import PropTypes from "prop-types"
151+
import Img from "gatsby-image"
152+
import styled from "styled-components"
153+
154+
const LightboxContainer = styled.div`
155+
display: grid;
156+
grid-template-columns: repeat(5, 1fr);
157+
grid-gap: 5px;
158+
`
159+
160+
export default class Lightbox extends Component {
161+
static propTypes = {
162+
carImages: PropTypes.array.isRequired, // eslint-disable-line
163+
}
164+
165+
static state = {
166+
open: false,
167+
}
168+
169+
render() {
170+
const { carImages } = this.props
171+
return (
172+
<LightboxContainer>
173+
{carImages.map(image => (
174+
<Img
175+
key={image.node.childImageSharp.fluid.src}
176+
fluid={image.node.childImageSharp.fluid}
177+
/>
178+
))}
179+
</LightboxContainer>
180+
)
181+
}
182+
}
183+
```
184+
185+
Now the page should look something like this:
186+
187+
![Displaying images on the home page](/screen-3.png)
188+
189+
### Creating the Lightbox
190+
191+
For the sake of accessibility, you'll be using [Dialog](https://ui.reach.tech/dialog) from Reach UI - shout out to [Ryan Florence](https://github.com/ryanflorence) for making awesome, accessible tools for React. As a side note, Gatsby's router is using [`@reach/router`](https://reach.tech/router) under the hood, so you're keeping it in the family 😉
192+
193+
Go ahead and install all the dependencies:
194+
195+
```terminal
196+
$ yarn add @reach/dialog
197+
```
198+
199+
And configure a basic dialog inside the `Lightbox` component.
200+
201+
```js
202+
import React, { Component, Fragment } from "react"
203+
import PropTypes from "prop-types"
204+
import Img from "gatsby-image"
205+
import styled from "styled-components"
206+
import { Dialog } from "@reach/dialog"
207+
import "@reach/dialog/styles.css"
208+
209+
const LightboxContainer = styled.div`
210+
display: grid;
211+
grid-template-columns: repeat(5, 1fr);
212+
grid-gap: 5px;
213+
`
214+
215+
export default class Lightbox extends Component {
216+
static propTypes = {
217+
carImages: PropTypes.array.isRequired, // eslint-disable-line
218+
}
219+
220+
constructor(props) {
221+
super(props)
222+
223+
this.state = {
224+
showLightbox: false,
225+
}
226+
}
227+
228+
render() {
229+
const { carImages } = this.props
230+
const { showLightbox } = this.state
231+
return (
232+
<Fragment>
233+
<LightboxContainer>
234+
{carImages.map(image => (
235+
<Img
236+
key={image.node.childImageSharp.fluid.src}
237+
fluid={image.node.childImageSharp.fluid}
238+
/>
239+
))}
240+
</LightboxContainer>
241+
<button
242+
type="button"
243+
onClick={() => this.setState({ showLightbox: true })}
244+
>
245+
Show Dialog
246+
</button>
247+
{showLightbox && (
248+
<Dialog>
249+
<p>Image will go here</p>
250+
<button
251+
type="button"
252+
onClick={() => this.setState({ showLightbox: false })}
253+
>
254+
Clos
255+
</button>
256+
</Dialog>
257+
)}
258+
</Fragment>
259+
)
260+
}
261+
}
262+
```
263+
264+
Now, when you click the `Show Dialog` button, you should see something like this:
265+
266+
![Basic Dialog implementation](/screen-2.png)
267+
268+
### Connecting the image click to the Lightbox
269+
270+
Now, you're going to convert each `Img` component into a button that you can click that will trigger the lightbox to open up with the right image selected. Here's how you do that:
271+
272+
```js
273+
import React, { Component, Fragment } from "react"
274+
import PropTypes from "prop-types"
275+
import Img from "gatsby-image"
276+
import styled from "styled-components"
277+
import { Dialog } from "@reach/dialog"
278+
import "@reach/dialog/styles.css"
279+
280+
const LightboxContainer = styled.div`
281+
display: grid;
282+
grid-template-columns: repeat(5, 1fr);
283+
grid-gap: 5px;
284+
`
285+
286+
const PreviewButton = styled.button`
287+
background: transparent;
288+
border: none;
289+
padding: 0;
290+
margin: 0;
291+
`
292+
293+
export default class Lightbox extends Component {
294+
static propTypes = {
295+
carImages: PropTypes.array.isRequired, // eslint-disable-line
296+
}
297+
298+
constructor(props) {
299+
super(props)
300+
301+
this.state = {
302+
showLightbox: false,
303+
selectedImage: null,
304+
}
305+
}
306+
307+
render() {
308+
const { carImages } = this.props
309+
const { selectedImage, showLightbox } = this.state
310+
return (
311+
<Fragment>
312+
<LightboxContainer>
313+
{carImages.map(image => (
314+
<PreviewButton
315+
key={image.node.childImageSharp.fluid.src}
316+
type="button"
317+
onClick={() =>
318+
this.setState({ showLightbox: true, selectedImage: image })
319+
}
320+
>
321+
<Img fluid={image.node.childImageSharp.fluid} />
322+
</PreviewButton>
323+
))}
324+
</LightboxContainer>
325+
{showLightbox && (
326+
<Dialog>
327+
<Img fluid={selectedImage.node.childImageSharp.fluid} />
328+
<button
329+
type="button"
330+
onClick={() => this.setState({ showLightbox: false })}
331+
>
332+
Close
333+
</button>
334+
</Dialog>
335+
)}
336+
</Fragment>
337+
)
338+
}
339+
}
340+
```
341+
342+
Now, if you click on one of the images, it should look something like this:
343+
344+
![Final Lightbox](/screen-1.png)
345+
346+
Now you can go ahead and implement this in your e-commerce store, picture gallery, or anywhere else you might see fit 😃
347+
348+
Feel free to [tweet me](https://twitter.com/416serg) if you have any questions, comments, requests or just want to show some ❤️.
Loading
Loading
Loading
Loading

docs/blog/author.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@
177177
avatar: avatars/michael-holtzman.jpg
178178
twitter: "@mikelax"
179179
bio: nyc • tech • I build things 🚀 & learn things 📖
180+
- id: Sergiy Dybskiy
181+
avatar: avatars/sergiy-dybskiy.jpg
182+
twitter: "@416serg"
183+
bio: JavaScript Developer ⚛️ Burrito enthusiast 🌯 Snowboarder 🏂🏼
180184
- id: Joaquín Bravo Contreras
181185
avatar: avatars/joaquin-bravo.jpg
182186
twitter: "@jackbravo"

docs/blog/avatars/sergiy-dybskiy.jpg

44.8 KB
Loading

0 commit comments

Comments
 (0)