Skip to content

Commit

Permalink
feat(PaginationItem): Expose the gallery-pagination-item component (#10)
Browse files Browse the repository at this point in the history
* feat(PaginationItem): Expose the gallery-pagination-item component

* refactor: Use explicit render prop

* docs: Updated Documentation to be clearer

* docs: minor tweaks

* refactor(Gallery): update `i` prop to `index` within renderGalleryItem

* 3.0.0

---------

Co-authored-by: Andrew Rubin <arubin78@gmail.com>
  • Loading branch information
Tiendongle and andrewrubin authored Oct 23, 2023
1 parent 1e6060d commit 81d1988
Showing 8 changed files with 143 additions and 34 deletions.
86 changes: 73 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -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>
)}
@@ -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>
)}
/>

@@ -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

@@ -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;
@@ -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/).
@@ -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 |
| ------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
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"
6 changes: 3 additions & 3 deletions src/lib/components/gallery-main.jsx
Original file line number Diff line number Diff line change
@@ -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>
)
16 changes: 14 additions & 2 deletions src/lib/components/gallery-pagination-item.jsx
Original file line number Diff line number Diff line change
@@ -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}
>
{children}
</button>
</li>
@@ -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
@@ -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>
)
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
@@ -6,6 +6,7 @@ import {
GalleryItem,
GalleryNav,
GalleryPagination,
GalleryPaginationItem,
useGallery,
} from "./lib"

@@ -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>
)}
@@ -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>
)

0 comments on commit 81d1988

Please sign in to comment.