Skip to content

Commit

Permalink
chore(Carousel): customizable button renderprops and added stories (#90)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: refactored fullWidth prop to width type
  • Loading branch information
benjitrosch committed Apr 15, 2022
1 parent edad1ca commit 506cbe5
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 20 deletions.
120 changes: 119 additions & 1 deletion src/Carousel/Carousel.stories.tsx
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import { Story, Meta } from '@storybook/react'

import Carousel, { CarouselProps } from '.'
import Button from '../Button'

export default {
title: 'Data Display/Carousel',
Expand All @@ -10,7 +11,7 @@ export default {

export const Default: Story<CarouselProps> = (args) => {
return (
<Carousel {...args}>
<Carousel {...args} className="rounded-box">
<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" />
Expand All @@ -22,3 +23,120 @@ export const Default: Story<CarouselProps> = (args) => {
)
}
Default.args = {}

export const Snap: Story<CarouselProps> = (args) => {
return (
<Carousel {...args} className="rounded-box">
<Carousel.Item src="https://api.lorem.space/image/drink?w=400&h=300&hash=8B7BCDC2" alt="Drinks" />
<Carousel.Item src="https://api.lorem.space/image/drink?w=400&h=300&hash=500B67FB" alt="Drinks" />
<Carousel.Item src="https://api.lorem.space/image/drink?w=400&h=300&hash=A89D0DE6" alt="Drinks" />
<Carousel.Item src="https://api.lorem.space/image/drink?w=400&h=300&hash=225E6693" alt="Drinks" />
<Carousel.Item src="https://api.lorem.space/image/drink?w=400&h=300&hash=9D9539E7" alt="Drinks" />
<Carousel.Item src="https://api.lorem.space/image/drink?w=400&h=300&hash=BDC01094" alt="Drinks" />
<Carousel.Item src="https://api.lorem.space/image/drink?w=400&h=300&hash=7F5AE56A" alt="Drinks" />
</Carousel>
)
}
Snap.args = {
snap: 'end'
}

export const FullWidth: Story<CarouselProps> = (args) => {
return (
<Carousel {...args} className="w-64 rounded-box">
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=8B7BCDC2" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=500B67FB" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=A89D0DE6" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=225E6693" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=9D9539E7" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=BDC01094" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=7F5AE56A" alt="Videogame Poster" />
</Carousel>
)
}
FullWidth.args = {
width: 'full',
}

export const HalfWidth: Story<CarouselProps> = (args) => {
return (
<Carousel {...args} className="w-64 rounded-box">
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=8B7BCDC2" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=500B67FB" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=A89D0DE6" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=225E6693" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=9D9539E7" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=BDC01094" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=7F5AE56A" alt="Videogame Poster" />
</Carousel>
)
}
HalfWidth.args = {
width: 'half',
}

export const Vertical: Story<CarouselProps> = (args) => {
return (
<Carousel {...args} className="h-96 w-64 rounded-box">
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=8B7BCDC2" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=500B67FB" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=A89D0DE6" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=225E6693" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=9D9539E7" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=BDC01094" alt="Videogame Poster" />
<Carousel.Item src="https://api.lorem.space/image/game?w=256&h=400&hash=7F5AE56A" alt="Videogame Poster" />
</Carousel>
)
}
Vertical.args = {
width: 'full',
vertical: true,
}

export const Numbered: Story<CarouselProps> = (args) => {
return (
<Carousel {...args} className="rounded-box">
<Carousel.Item src="https://api.lorem.space/image/car?w=800&h=200&hash=8B7BCDC2" alt="Car" />
<Carousel.Item src="https://api.lorem.space/image/car?w=800&h=200&hash=500B67FB" alt="Car" />
<Carousel.Item src="https://api.lorem.space/image/car?w=800&h=200&hash=A89D0DE6" alt="Car" />
<Carousel.Item src="https://api.lorem.space/image/car?w=800&h=200&hash=225E6693" alt="Car" />
</Carousel>
)
}
Numbered.args = {
display: 'numbered',
}

export const Sequential: Story<CarouselProps> = (args) => {
return (
<Carousel {...args} className="rounded-box">
<Carousel.Item src="https://api.lorem.space/image/car?w=800&h=200&hash=8B7BCDC2" alt="Car" />
<Carousel.Item src="https://api.lorem.space/image/car?w=800&h=200&hash=500B67FB" alt="Car" />
<Carousel.Item src="https://api.lorem.space/image/car?w=800&h=200&hash=A89D0DE6" alt="Car" />
<Carousel.Item src="https://api.lorem.space/image/car?w=800&h=200&hash=225E6693" alt="Car" />
</Carousel>
)
}
Sequential.args = {
display: 'sequential',
}

export const CustomButton: Story<CarouselProps> = (args) => {
const buttonStyle = (value: string) => {
return <Button color="primary">{value}</Button>
}

args.buttonStyle = buttonStyle

return (
<Carousel {...args} className="rounded-box">
<Carousel.Item src="https://api.lorem.space/image/car?w=800&h=200&hash=8B7BCDC2" alt="Car" />
<Carousel.Item src="https://api.lorem.space/image/car?w=800&h=200&hash=500B67FB" alt="Car" />
<Carousel.Item src="https://api.lorem.space/image/car?w=800&h=200&hash=A89D0DE6" alt="Car" />
<Carousel.Item src="https://api.lorem.space/image/car?w=800&h=200&hash=225E6693" alt="Car" />
</Carousel>
)
}
CustomButton.args = {
display: 'sequential',
}
34 changes: 26 additions & 8 deletions src/Carousel/Carousel.tsx
Expand Up @@ -12,7 +12,7 @@ import { twMerge } from 'tailwind-merge'

import { IComponentBaseProps } from '../types'

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

export type CarouselProps = React.HTMLAttributes<HTMLDivElement> &
Expand All @@ -21,12 +21,22 @@ export type CarouselProps = React.HTMLAttributes<HTMLDivElement> &
display?: 'slider' | 'numbered' | 'sequential'
snap?: 'start' | 'center' | 'end'
vertical?: boolean
fullWidth?: boolean
width?: CarouselItemWidth
buttonStyle?: (value: string) => React.ReactElement
}

const Carousel = forwardRef<HTMLDivElement, CarouselProps>(
(
{ children, display = 'slider', snap, vertical, fullWidth, dataTheme, className, ...props },
{ children,
display = 'slider',
snap,
vertical,
width,
buttonStyle,
dataTheme,
className,
...props
},
ref
): JSX.Element => {
const classes = twMerge(
Expand Down Expand Up @@ -69,15 +79,15 @@ const Carousel = forwardRef<HTMLDivElement, CarouselProps>(
>
{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),
width: display !== 'slider' ? 'full' : width,
hasButtons: display === 'sequential',
buttonStyle,
onPrev: () => scrollToIndex(i - 1 < 0 ? children.length - 1 : i - 1),
onNext: () => scrollToIndex(i + 1 > children.length - 1 ? 0 : i + 1),
...child.props,
})
Expand All @@ -86,8 +96,16 @@ const Carousel = forwardRef<HTMLDivElement, CarouselProps>(
{display === 'numbered' && (
<div className='flex justify-center w-full py-2 gap-2'>
{children.map((_, i) => {
if (buttonStyle != null) {
return (
cloneElement(buttonStyle((i + 1).toString()), {
key: i,
onClick: () => scrollToIndex(i)
})
)
}

return (
// TODO: pass in customizable numbered buttons
<Button key={i} onClick={() => scrollToIndex(i)}>
{i + 1}
</Button>
Expand Down
51 changes: 40 additions & 11 deletions src/Carousel/CarouselItem.tsx
@@ -1,16 +1,19 @@
import React, { LegacyRef } from 'react'
import React, { cloneElement, LegacyRef } from 'react'
import clsx from 'clsx'
import { twMerge } from 'tailwind-merge'

import Button from '../Button'

export type CarouselItemWidth = 'full' | 'half'

export type CarouselItemProps = React.HTMLAttributes<HTMLDivElement> & {
readonly innerRef?: LegacyRef<HTMLDivElement>
src?: string
alt?: string
index?: number
buttons?: boolean
fullWidth?: boolean
width?: CarouselItemWidth
hasButtons?: boolean
buttonStyle?: (value: string) => React.ReactElement
onPrev?: () => void
onNext?: () => void
}
Expand All @@ -21,30 +24,56 @@ const CarouselItem = ({
src,
alt,
index = 0,
buttons,
fullWidth,
width,
hasButtons,
buttonStyle,
onPrev,
onNext,
className,
...props
}: CarouselItemProps): JSX.Element => {
const classes = twMerge(
"carousel-item relative",
className
className,
clsx({
'w-full': width === 'full',
'w-1/2': width === 'half',
'h-full': true,
})
)

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

const renderButtons = () => {
if (buttonStyle != null) {
return (
<>
{cloneElement(buttonStyle('❮'), {
onClick: onPrev
})}
{cloneElement(buttonStyle('❯'), {
onClick: onNext
})}
</>
)
}

return (
<>
<Button onClick={onPrev} shape="circle"></Button>
<Button onClick={onNext} shape="circle"></Button>
</>
)
}

return (
<div {...props} id={`item${index}`} ref={innerRef} className={classes}>
{src ? <img src={src} alt={alt} className={imageClasses} /> : children}
{buttons && (
{hasButtons && (
<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>
{renderButtons()}
</div>
)}
</div>
Expand Down

0 comments on commit 506cbe5

Please sign in to comment.