-
-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature(website/ui): add carousel component (#571)
- Loading branch information
Showing
51 changed files
with
818 additions
and
230 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,11 @@ | ||
{ | ||
"section-1": { | ||
"section-our-work": { | ||
"title-1": "Wir bauen das Fundament für echte Veränderung. ", | ||
"title-2": "Zeitgemäss. Digital. Gemeinsam." | ||
"title-2": "Zeitgemäss. Digital. Gemeinsam.", | ||
"vimeo-video-id": "488184818" | ||
}, | ||
"section-how-it-works": { | ||
"title": "Ein solidarischer Beitrag, von Mensch zu Mensch. So einfach.", | ||
"subtitle": "Mach einen Unterschied in weniger als 1 Minute." | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,24 @@ | ||
{ | ||
"section-1": { | ||
"section-our-work": { | ||
"title-1": "Our work is made possible by you. Make a ", | ||
"title-2": "change with us." | ||
"title-2": "change with us.", | ||
"vimeo-video-id": "433937157" | ||
}, | ||
"section-how-it-works": { | ||
"title": "From you to someone in need. This is solidarity.", | ||
"subtitle": "Make a difference in under a minute.", | ||
"item-1": "100% of individual contributions are paid out to recipients", | ||
"item-2": "Flexible payment schedules. Adjust or cancel anytime", | ||
"item-3": "Social Income payments are sent directly to recipients’ mobile phones.", | ||
"item-4": "The amount disbursed is determined by the average salary where recipients live." | ||
}, | ||
"section-contributors": { | ||
"header-1": "Contributors", | ||
"title-1": "People from all walks of life contribute.", | ||
"text-1": "We receive, amongst others, regular individual contributions from employees at the following organisations and companies:", | ||
|
||
"header-2": "Why contribute?", | ||
"title-2": "One goal. Greater social justice.", | ||
"text-2": "Our contributors want to help people for a variety of reasons. Here are just a few driving forces behind the humanitarians who contribute their 1% to Social Income:" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
'use client'; | ||
|
||
import Autoplay, { AutoplayOptionsType } from 'embla-carousel-autoplay'; | ||
import useEmblaCarousel, { EmblaOptionsType } from 'embla-carousel-react'; | ||
import React, { useContext, useEffect } from 'react'; | ||
import { cn } from '../lib/utils'; | ||
|
||
export const CarouselContext = React.createContext<EmblaOptionsType>({}); | ||
|
||
type CarouselProps = { | ||
options?: EmblaOptionsType & { autoPlay?: { enabled: boolean } & AutoplayOptionsType }; | ||
showDots?: boolean; | ||
} & React.HTMLAttributes<HTMLDivElement>; | ||
|
||
export const Carousel = ({ children, className, options = {}, showDots = true, ...props }: CarouselProps) => { | ||
const [emblaRef, emblaApi] = useEmblaCarousel( | ||
options, | ||
options?.autoPlay?.enabled ? [Autoplay(options.autoPlay)] : [], | ||
); | ||
const [emblaOptions, setEmblaOptions] = React.useState<EmblaOptionsType>(options); | ||
const [selectedIndex, setSelectedIndex] = React.useState(0); | ||
const slidesCount = React.Children.count(children); | ||
|
||
useEffect(() => { | ||
function selectHandler() { | ||
const index = emblaApi?.selectedScrollSnap(); | ||
setSelectedIndex(index || 0); | ||
} | ||
emblaApi?.on('select', selectHandler); | ||
|
||
return () => { | ||
emblaApi?.off('select', selectHandler); | ||
}; | ||
}, [emblaApi]); | ||
|
||
useEffect(() => { | ||
setEmblaOptions(options); | ||
}, [options]); | ||
|
||
return ( | ||
<CarouselContext.Provider value={emblaOptions}> | ||
<div className={cn('overflow-hidden', className)} ref={emblaRef} {...props}> | ||
<div className="flex">{children}</div> | ||
</div> | ||
{showDots && ( | ||
<CarouselDots | ||
itemsLength={Math.ceil(slidesCount / (Number(options.slidesToScroll) || 1))} | ||
selectedIndex={selectedIndex} | ||
onClick={(index) => emblaApi?.scrollTo(index)} | ||
/> | ||
)} | ||
|
||
<CarouselControls | ||
canScrollNext={!!emblaApi?.canScrollNext()} | ||
canScrollPrev={!!emblaApi?.canScrollPrev()} | ||
onNext={() => emblaApi?.scrollNext()} | ||
onPrev={() => emblaApi?.scrollPrev()} | ||
/> | ||
</CarouselContext.Provider> | ||
); | ||
}; | ||
|
||
export const CarouselContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>( | ||
({ children, className, ...props }, ref) => { | ||
const options = useContext(CarouselContext); | ||
return ( | ||
<div | ||
ref={ref} | ||
className={cn( | ||
'relative flex-shrink-0 flex-grow-0', | ||
{ | ||
auto: 'auto', | ||
1: 'basis-full', | ||
2: 'basis-1/2', | ||
3: 'basis-1/3', | ||
4: 'basis-1/4', | ||
5: 'basis-1/5', | ||
6: 'basis-1/6', | ||
}[options?.slidesToScroll || 'auto'], | ||
className, | ||
)} | ||
{...props} | ||
> | ||
{children} | ||
</div> | ||
); | ||
}, | ||
); | ||
|
||
type CarouselDots = { | ||
itemsLength: number; | ||
selectedIndex: number; | ||
onClick?: (index: number) => void; | ||
}; | ||
|
||
const CarouselDots = ({ itemsLength, selectedIndex, onClick }: CarouselDots) => { | ||
const arr = new Array(itemsLength).fill(0); | ||
return ( | ||
<div className="my-2 flex -translate-y-5 justify-center gap-1"> | ||
{arr.map((_, index) => { | ||
const selected = index === selectedIndex; | ||
return ( | ||
<div | ||
key={index} | ||
className={cn('h-2 w-2 rounded-full bg-indigo-400 transition-all duration-300', { | ||
'opacity-50': !selected, | ||
})} | ||
onClick={() => onClick?.(index)} | ||
></div> | ||
); | ||
})} | ||
</div> | ||
); | ||
}; | ||
|
||
type Props = { | ||
canScrollPrev: boolean; | ||
canScrollNext: boolean; | ||
onPrev(): void; | ||
onNext(): void; | ||
}; | ||
const CarouselControls = (props: Props) => { | ||
return ( | ||
<div className="flex justify-end gap-2 "> | ||
<button | ||
onClick={() => { | ||
if (props.canScrollPrev) { | ||
props.onPrev(); | ||
} | ||
}} | ||
disabled={!props.canScrollPrev} | ||
className={cn('rounded-md px-4 py-2 text-white', { | ||
'bg-indigo-200': !props.canScrollPrev, | ||
'bg-indigo-400': props.canScrollPrev, | ||
})} | ||
> | ||
Prev | ||
</button> | ||
<button | ||
onClick={() => { | ||
if (props.canScrollNext) { | ||
props.onNext(); | ||
} | ||
}} | ||
disabled={!props.canScrollNext} | ||
className={cn('rounded-md px-4 py-2 text-white', { | ||
'bg-indigo-200': !props.canScrollNext, | ||
'bg-indigo-400': props.canScrollNext, | ||
})} | ||
> | ||
Next | ||
</button> | ||
</div> | ||
); | ||
}; | ||
export default CarouselControls; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,21 @@ | ||
import classNames from 'classnames'; | ||
import { PropsWithChildren } from 'react'; | ||
import React from 'react'; | ||
import { twMerge } from 'tailwind-merge'; | ||
import { BackgroundColor } from '../../interfaces/color'; | ||
|
||
interface BaseContainerProps { | ||
className?: string; | ||
} | ||
type BaseContainerProps = { | ||
backgroundColor?: BackgroundColor; | ||
} & React.HTMLAttributes<HTMLDivElement>; | ||
|
||
export function BaseContainer({ children, className }: PropsWithChildren<BaseContainerProps>) { | ||
return ( | ||
<div className={className}> | ||
<div className={classNames('mx-auto max-w-6xl px-3 md:px-5')}>{children}</div> | ||
</div> | ||
); | ||
} | ||
export const BaseContainer = React.forwardRef<HTMLDivElement, BaseContainerProps>( | ||
({ children, className, backgroundColor, ...props }, ref) => { | ||
return ( | ||
<div className={backgroundColor}> | ||
<div className="mx-auto max-w-6xl px-3 md:px-6"> | ||
<div className={twMerge(backgroundColor, className)} ref={ref} {...props}> | ||
{children} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.