Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 73 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,21 @@ export const GALLERY_ITEMS = [
// your-gallery.js

import { GALLERY_ITEMS } from "./some-data"
import { Gallery, GalleryMain, GalleryNav, GalleryPagination, GalleryItem } from "@wethegit/react-gallery"
import { Gallery,
GalleryMain,
GalleryNav,
GalleryPagination,
GalleryPaginationItem,
GalleryItem,
} from "@wethegit/react-gallery"

const YourGallery = () => {
return (
<Gallery items={GALLERY_ITEMS}>

<GalleryMain
renderGalleryItem={({ item, i, active }) => (
<GalleryItem key={i} index={i} active={active}>
renderGalleryItem={({ item, index, active }) => (
<GalleryItem key={item.id} index={i} active={active}>
<img src={item.image} alt={item.alt} />
</GalleryItem>
)}
Expand All @@ -96,8 +102,10 @@ const YourGallery = () => {
<GalleryNav direction={1}>➡️</GalleryNav>

<GalleryPagination
renderPaginationItem={({ i }) => (
<span>{i + 1}</span>
renderPaginationItem={({ item, index, active }) => (
<GalleryPaginationItem key={item.id} index={index} active={active}>
<span>{i + 1}</span>
</GalleryPaginationItem>
)}
/>

Expand All @@ -110,11 +118,11 @@ export default YourGallery

The first step is to give your data to the `<Gallery>` component via the `items` prop. At the very least, `items` is expected to be an Array. From there, you're free to arrange the child components this package provides as you see fit. Below is a brief description of each of the child components' usage. For a detailed breakdown of this component, jump ahead to the [Gallery](#gallery) section.

`<GalleryMain>` is the primary gallery view where your item data is rendered. It receives a render prop, `renderGalleryItem`, which exposes a few arguments you can use in the JSX you return: `item`, `i`, `activeIndex`, and `active` and expects a `<GalleryItem>` to be returned. For a detailed breakdown of this component, jump ahead to the [GalleryMain](#gallerymain) section.
`<GalleryMain>` is the primary gallery view where your item data is rendered. It receives a render prop, `renderGalleryItem`, which exposes a few arguments you can use in the JSX you return: `item`, `index`, `activeIndex`, and `active` and expects a `<GalleryItem>` to be returned. For a detailed breakdown of this component, jump ahead to the [GalleryMain](#gallerymain) section.

We're using the `<GalleryNav>` component to define our "next" and "previous" buttons. These components receive a `direction` prop, which expects either a `1` or a `0`, and corresponds to the direction the gallery should move in when the button in question is clicked (where `0` maps to "previous", and `1` maps to "next"). For a detailed breakdown of this component, see the [GalleryNav](#gallerynav) section.

We're also using the `<GalleryPagination>` component here. If you're not familiar, "pagination" refers to what is often rendered as a set of "dots" below a gallery — but this can be _anything_ (thumbnails, icons, and so on). This component receives the render prop, `renderPaginationItem`, which exposes a few arguments you can use in the JSX you return: `item`, `i`, `activeIndex`, and `active`. For a detailed breakdown of this component, jump ahead to the [GalleryPagination](#gallerypagination) section.
We're also using the `<GalleryPagination>` and `GalleryPaginationItem` components here. If you're not familiar, "pagination" refers to what is often rendered as a set of "dots" below a gallery — but this can be _anything_ (thumbnails, icons, and so on). This component receives the render prop, `renderPaginationItem`, which exposes a few arguments you can use in the JSX you return: `item`, `i`, `activeIndex`, and `active`. The easiest way to link up you pagination is to use the `<GalleryPaginationItem>` component, as shown in the example above. For a detailed breakdown of this component, jump ahead to the [GalleryPagination](#gallerypagination) section.

## Custom layouts

Expand Down Expand Up @@ -188,7 +196,7 @@ This render prop expects a `<GalleryItem>` to be returned, and receives a handfu
| ----------- | ------- | -------------------------------------------------------------------------------------------------------------- |
| active | Boolean | Whether the current item being iterated over is the active item. |
| activeIndex | Number | The index of the currently active gallery item. |
| i | Number | The index of the current item being iterated over. |
| index | Number | The index of the current item being iterated over. |
| item | Any | The current item being iterated over, as defined by the Array fed to the `<Gallery>` component's `items` prop. |

### &lt;GalleryItem&gt;
Expand Down Expand Up @@ -237,15 +245,62 @@ Renders an unordered list (`<ul>`) of pagination items. Must be used within a `<

#### `renderPaginationItem`

This render prop wraps its return value in a list item (`<li>`) and a `<button>`, and receives a handful of arguments:
This render prop receives a handful of arguments, and is necessary for rendering pagination UI:

| Argument | Type | Description |
| ----------- | ------- | ------------------------------------------------------------------------------------------------------------------------- |
| active | Boolean | Whether the current pagination item being iterated over corresponds to the active gallery item. |
| activeIndex | Number | The index of the currently active gallery item. |
| i | Number | The index of the current pagination item being iterated over. |
| index | Number | The index of the current pagination item being iterated over. |
| item | Any | The current pagination item being iterated over, as defined by the Array fed to the `<Gallery>` component's `items` prop. |

**Example usage of `renderPaginationItem` render prop**
```jsx
<GalleryPagination
renderPaginationItem={({ index, active, activeIndex, item }) => (
<GalleryPaginationItem index={index} active={active} key={item.id}>
<span>{index + 1}</span>
</GalleryPaginationItem>
)}
/>
```
### &lt;GalleryPaginationItem&gt;

Used in the prop `renderPaginationItem` of `<GalleryPagination>`. This component with a return value in a list item (`<li>`) and a `<button>`, and receives a handful of arguments:

#### Props:


| Prop | Type | Description |
| ------------------ | -------- | --------------------------------------------------------------------------------------------------------------- |
| active | Boolean | **Required**. Boolean to set the `<button>`'s `aria-current` attribute. |
| buttonClassName | String | Set the `<button>` element's class. |
| buttonProps | Object | Pass props to the `<button>` element. |
| children | JSX | Pass children to the component to render them as children of the implicit `<button>` element. |
| className | String | Set the `<li>` element's class. |
| index | Number | **Required**. This needs to be a unique identifier for the `<li>` element, corresponding to the index of the Gallery Item being iterated over. It is used to set the gallery's active item to the associated pagination item button clicked. |
| onClick | Function | This is a curried callback function to hook into the `onClick` handler on the `<button>` element. The curried callback returns an object containing `{event,index}`. `event` is a `MouseClickEvent` and `index` is the index of the *PaginationItem*. Note that this is specific to the pagination buttons; if you want a piece of code to run when the active item changes _regarless_ of what triggered that change, opt for the `onChange` callback instead (passed to the `<Gallery>` component.) |

**Example usage of `onClick` prop**
```jsx
const handlePaginationItemClick = ({ event, index }) => {
console.log(event, index)
}

<GalleryPagination
renderPaginationItem={({ index, active, item }) => (
<GalleryPaginationItem
index={index}
active={active}
key={item.id}
onClick={handlePaginationItemClick}
>
<span>{index + 1}</span>
</GalleryPaginationItem>
)}
/>
```

## Accessibility

The gallery component handles tabbing, focus management, and live-region announcements out-of-the-box. All relevant patterns used in this component follow the guidelines for carousels as documented by the [Web Accessibility Initiative](https://www.w3.org/WAI/).
Expand Down Expand Up @@ -274,17 +329,22 @@ const YourGallery = () => {
// Pass the style overrides to the <Gallery> component
<Gallery items={GALLERY_ITEMS} style={style}>
<GalleryMain
renderGalleryItem={({ item }) => <img src={item.image} alt={item.alt} />}
renderGalleryItem={({ item, index, active }) => (
<GalleryItem key={item.id} index={index} active={active}>
<img src={item.image} alt={item.alt} />
</GalleryItem>
)}
/>
// ...etc
{/* ...etc */}
</Gallery>
)
}
```

## useGallery hook

The gallery package exposes a `useGallery` React hook. It returns a single object, the properties of which are outlined below:
The gallery package exposes a `useGallery` React hook. It returns a single object, the properties of which are outlined below.
⚠️ `useGallery` _must_ be called from within a `<Gallery>` context.

| Property | Type | Description |
| ------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@wethegit/react-gallery",
"version": "2.0.1",
"version": "3.0.0",
"description": "A customizable, accessible gallery component for React projects.",
"files": [
"dist"
Expand Down
6 changes: 3 additions & 3 deletions src/lib/components/gallery-main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ export const GalleryMain = ({ renderGalleryItem, className, ...props }) => {
style={{ "--selected": activeIndex, "--total": galleryItems.length }}
{...props}
>
{galleryItems.map((item, i) => {
const active = activeIndex === i
return renderGalleryItem({ item, i, activeIndex, active })
{galleryItems.map((item, index) => {
const active = activeIndex === index
return renderGalleryItem({ item, index, activeIndex, active })
})}
</ul>
)
Expand Down
16 changes: 14 additions & 2 deletions src/lib/components/gallery-pagination-item.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,28 @@ export const GalleryPaginationItem = ({
index,
active,
className,
buttonClassName,
buttonProps,
children,
onClick,
...props
}) => {
const { goToIndex, itemNodes } = useGallery()

const handleClick = (i) => {
const handleClick = (i) => (event) => {
goToIndex(i)
itemNodes.current[i].focus({ preventScroll: true })
onClick?.({ event, index })
}

return (
<li className={classnames(["gallery__pagination-item", className])} {...props}>
<button onClick={() => handleClick(index)} aria-current={active ? "true" : null}>
<button
className={buttonClassName}
onClick={handleClick(index)}
aria-current={active ? "true" : null}
{...buttonProps}
Comment thread
Tiendongle marked this conversation as resolved.
>
{children}
</button>
</li>
Expand All @@ -34,5 +43,8 @@ GalleryPaginationItem.propTypes = {
index: PropTypes.number.isRequired,
active: PropTypes.bool.isRequired,
className: PropTypes.string,
buttonClassName: PropTypes.string,
buttonProps: PropTypes.object,
children: PropTypes.node,
onClick: PropTypes.func,
}
39 changes: 29 additions & 10 deletions src/lib/components/gallery-pagination.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,43 @@ import PropTypes from "prop-types"
// hooks
import { useGallery } from "../hooks/use-gallery"

// components
import { GalleryPaginationItem } from "./gallery-pagination-item"

// utils
import classnames from "../utils/classnames"

/***
*
* Pagination Item component callback
* ---
* @callback renderPaginationItem - Expects a component of GalleryPaginationItem
* @param {number} index - The index of the current pagination item being iterated over.
* @param {boolean} active - Whether the current pagination item being iterated over corresponds to the active gallery item.
* @param {number} activeIndex - The index of the currently active gallery item.
* @param {any} item - The current pagination item being iterated over, as defined by the Array fed to the `<Gallery>` component's `items` prop.
*/

/**
* Pagination Component
* ---
* @param {object} props
* @param {renderPaginationItem} props.renderPaginationItem - The component to be rendered. This expects the main wrapper to be a GalleryPaginationItem component
* @param {string} [props.className] - Pass classname to <ul> element
* @example
* <GalleryPagination
* renderPaginationItem={({ index, active }) => (
* <GalleryPaginationItem index={index} active={active} key={index}>
* <span>{index + 1}</span>
* </GalleryPaginationItem>
* )}
* />
*/
export const GalleryPagination = ({ renderPaginationItem, className, ...props }) => {
const { activeIndex, galleryItems } = useGallery()

return (
<ul className={classnames(["gallery__pagination", className])} {...props}>
{galleryItems.map((item, i) => {
const active = activeIndex === i
return (
<GalleryPaginationItem index={i} active={active} key={i}>
{renderPaginationItem({ item, i, activeIndex, active })}
</GalleryPaginationItem>
)
{galleryItems.map((item, index) => {
const active = activeIndex === index
return renderPaginationItem({ index, active, activeIndex, item })
})}
</ul>
)
Expand Down
1 change: 1 addition & 0 deletions src/lib/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { Gallery } from "./components/gallery-context"
export { GalleryMain } from "./components/gallery-main"
export { GalleryPagination } from "./components/gallery-pagination"
export { GalleryPaginationItem } from "./components/gallery-pagination-item"
export { GalleryNav } from "./components/gallery-nav"
export { GalleryItem } from "./components/gallery-item"
export { useGallery } from "./hooks/use-gallery"
23 changes: 20 additions & 3 deletions src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
GalleryItem,
GalleryNav,
GalleryPagination,
GalleryPaginationItem,
useGallery,
} from "./lib"

Expand Down Expand Up @@ -48,11 +49,16 @@ function GalleryDescription() {
}

function App() {
// Example of custom onClick handler
const handlePaginationItemClick = ({ event, index }) => {
console.log(event, index)
}

return (
<Gallery items={GALLERY_ITEMS}>
<GalleryMain
renderGalleryItem={({ item, i, active }) => (
<GalleryItem key={i} index={i} active={active}>
renderGalleryItem={({ item, index, active }) => (
<GalleryItem key={item.id} index={index} active={active}>
<img src={item.image} alt={item.alt} />
</GalleryItem>
)}
Expand All @@ -61,7 +67,18 @@ function App() {
<GalleryNav direction={0}>⬅️</GalleryNav>
<GalleryNav direction={1}>➡️</GalleryNav>

<GalleryPagination renderPaginationItem={({ i }) => <span>{i + 1}</span>} />
<GalleryPagination
renderPaginationItem={({ index, active, item }) => (
<GalleryPaginationItem
index={index}
active={active}
key={item.id}
onClick={handlePaginationItemClick}
>
<span>{index + 1}</span>
</GalleryPaginationItem>
)}
/>
<GalleryDescription />
</Gallery>
)
Expand Down