Skip to content

Commit

Permalink
chore(Carousel): added controls for alternative display modes (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjitrosch committed Mar 31, 2022
1 parent 0e0edd3 commit d2d254a
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 28 deletions.
14 changes: 7 additions & 7 deletions src/Carousel/Carousel.stories.tsx
Expand Up @@ -11,13 +11,13 @@ export default {
export const Default: Story<CarouselProps> = (args) => {
return (
<Carousel {...args}>
<Carousel.Item src="https://picsum.photos/id/500/256/144" />
<Carousel.Item src="https://picsum.photos/id/501/256/144" />
<Carousel.Item src="https://picsum.photos/id/502/256/144" />
<Carousel.Item src="https://picsum.photos/id/503/256/144" />
<Carousel.Item src="https://picsum.photos/id/504/256/144" />
<Carousel.Item src="https://picsum.photos/id/505/256/144" />
<Carousel.Item src="https://picsum.photos/id/506/256/144" />
<Carousel.Item src="https://api.lorem.space/image/burger?w=400&h=300&hash=8B7BCDC2" alt="Burger" />
<Carousel.Item src="https://api.lorem.space/image/burger?w=400&h=300&hash=500B67FB" alt="Burger" />
<Carousel.Item src="https://api.lorem.space/image/burger?w=400&h=300&hash=A89D0DE6" alt="Burger" />
<Carousel.Item src="https://api.lorem.space/image/burger?w=400&h=300&hash=225E6693" alt="Burger" />
<Carousel.Item src="https://api.lorem.space/image/burger?w=400&h=300&hash=9D9539E7" alt="Burger" />
<Carousel.Item src="https://api.lorem.space/image/burger?w=400&h=300&hash=BDC01094" alt="Burger" />
<Carousel.Item src="https://api.lorem.space/image/burger?w=400&h=300&hash=7F5AE56A" alt="Burger" />
</Carousel>
)
}
Expand Down
86 changes: 71 additions & 15 deletions src/Carousel/Carousel.tsx
@@ -1,23 +1,32 @@
import React, { forwardRef, ReactElement } from 'react'
import React, {
cloneElement,
createRef,
forwardRef,
ReactElement,
RefObject,
useEffect,
useState
} from 'react'
import clsx from 'clsx'
import { twMerge } from 'tailwind-merge'

import { IComponentBaseProps } from '@/types'

import CarouselItem, { CarouselItemProps } from './CarouselItem'
import Button from '../Button'

export type CarouselProps = React.HTMLAttributes<HTMLDivElement> &
IComponentBaseProps & {
children?:
| ReactElement<CarouselItemProps>
| ReactElement<CarouselItemProps>[]
children: ReactElement<CarouselItemProps>[]
display?: 'slider' | 'numbered' | 'sequential'
snap?: 'start' | 'center' | 'end'
vertical?: boolean
fullWidth?: boolean
}

const Carousel = forwardRef<HTMLDivElement, CarouselProps>(
(
{ children, snap, vertical, dataTheme, className, ...props },
{ children, display = 'slider', snap, vertical, fullWidth, dataTheme, className, ...props },
ref
): JSX.Element => {
const classes = twMerge(
Expand All @@ -26,20 +35,67 @@ const Carousel = forwardRef<HTMLDivElement, CarouselProps>(
clsx({
[`carousel-${snap}`]: snap,
'carousel-vertical': vertical,
'w-full': display !== 'slider'
})
)

const [itemRefs, setItemRefs] = useState<RefObject<HTMLDivElement>[]>([])

useEffect(() => {
const newRefs: RefObject<HTMLDivElement>[] = []
children.map((_) => {
newRefs.push(createRef<HTMLDivElement>())
})
setItemRefs(newRefs)
}, [children])

const scrollToIndex = (index: number) => {
itemRefs[index].current?.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: snap,
})
}

return (
<div
role="listbox"
aria-label="Image carousel"
{...props}
ref={ref}
data-theme={dataTheme}
className={classes}
>
{children}
</div>
<>
<div
role="listbox"
aria-label="Image carousel"
{...props}
ref={ref}
data-theme={dataTheme}
className={classes}
>
{children.map((child, i) => {
return cloneElement(child, {
className: display !== 'slider' || fullWidth ? 'w-full' : '',
innerRef: itemRefs[i],
index: i + 1,
children: child.props.children,
src: child.props.src,
alt: child.props.alt,
buttons: display === 'sequential',
fullWidth: display !== 'slider' || fullWidth,
onPrev: () => scrollToIndex(i - 1 <= 0 ? children.length - 1 : i - 1),
onNext: () => scrollToIndex(i + 1 > children.length - 1 ? 0 : i + 1),
...child.props,
})
})}
</div>
{display === 'numbered' && (
<div className='flex justify-center w-full py-2 gap-2'>
{children.map((_, i) => {
return (
// TODO: pass in customizable numbered buttons
<Button key={i} onClick={() => scrollToIndex(i)}>
{i + 1}
</Button>
)
})}
</div>
)}
</>
)
}
)
Expand Down
45 changes: 39 additions & 6 deletions src/Carousel/CarouselItem.tsx
@@ -1,19 +1,52 @@
import React, { ReactNode } from 'react'
import React, { LegacyRef } from 'react'
import clsx from 'clsx'
import { twMerge } from 'tailwind-merge'

export type CarouselItemProps = {
children?: ReactNode | ReactNode[]
import Button from '../Button'

export type CarouselItemProps = React.HTMLAttributes<HTMLDivElement> & {
readonly innerRef?: LegacyRef<HTMLDivElement>
src?: string
alt?: string
index?: number
buttons?: boolean
fullWidth?: boolean
onPrev?: () => void
onNext?: () => void
}

const CarouselItem = ({
children,
innerRef,
src,
index,
alt,
index = 0,
buttons,
fullWidth,
onPrev,
onNext,
className,
...props
}: CarouselItemProps): JSX.Element => {
const classes = twMerge(
"carousel-item relative",
className
)

const imageClasses = clsx({
'w-full': fullWidth
})

return (
<div id={`item${index}`} className="carousel-item">
{src ? <img src={src} /> : children}
<div {...props} id={`item${index}`} ref={innerRef} className={classes}>
{src ? <img src={src} alt={alt} className={imageClasses} /> : children}
{buttons && (
<div className="absolute flex justify-between transform -translate-y-1/2 left-5 right-5 top-1/2">
{/* TODO: pass in customizable prev/next buttons */}
<Button onClick={onPrev} shape="circle"></Button>
<Button onClick={onNext} shape="circle"></Button>
</div>
)}
</div>
)
}
Expand Down

0 comments on commit d2d254a

Please sign in to comment.