Skip to content

Commit

Permalink
feature(website/ui): add carousel component (#571)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkue authored Sep 25, 2023
1 parent 8528a21 commit aaf0a1e
Show file tree
Hide file tree
Showing 51 changed files with 818 additions and 230 deletions.
35 changes: 35 additions & 0 deletions package-lock.json

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

9 changes: 7 additions & 2 deletions shared/locales/de/website-our-work.json
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."
}
}
4 changes: 3 additions & 1 deletion shared/locales/en/website-common.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
"team": "Team",
"how-it-works": "How It Works",
"how-it-works-description": "Subtitle...",
"contributors": "Spender:innen",
"contributors": "Contributors",
"contributors-description": "Subtitle...",
"recipients": "Recipients",
"recipients-description": "Subtitle...",

"transparency": "Transparency",
"finances": "Finanzen",
Expand Down
22 changes: 20 additions & 2 deletions shared/locales/en/website-our-work.json
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:"
}
}
2 changes: 2 additions & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
"classnames": "^2.3.2",
"clsx": "^1.2.1",
"cmdk": "^0.2.0",
"embla-carousel-autoplay": "^8.0.0-rc14",
"embla-carousel-react": "^8.0.0-rc14",
"lucide-react": "^0.276.0",
"react-hook-form": "^7.46.1",
"tailwindcss-animate": "^1.0.7",
Expand Down
156 changes: 156 additions & 0 deletions ui/src/components/carousel.tsx
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;
31 changes: 19 additions & 12 deletions ui/src/components/containers/base-container.tsx
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>
);
},
);
2 changes: 1 addition & 1 deletion ui/src/components/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg md:w-full',
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-8 shadow-lg duration-200 sm:rounded-lg md:w-full',
className,
)}
{...props}
Expand Down
56 changes: 10 additions & 46 deletions ui/src/components/typography/typography.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import classNames from 'classnames';
import { ComponentPropsWithoutRef } from 'react';
import { twMerge } from 'tailwind-merge';
import { Color } from '../../interfaces/color';
import { FontColor } from '../../interfaces/color';
import { Size } from '../../interfaces/size';
import IntrinsicElements = React.JSX.IntrinsicElements;

Expand All @@ -26,51 +26,15 @@ const FONT_WEIGHT_MAP: { [key in FontWeight]: string } = {
bold: 'font-bold',
};

type FontColor = Extract<
Color,
| 'base-content'
| 'primary'
| 'primary-content'
| 'secondary'
| 'secondary-focus'
| 'secondary-content'
| 'accent'
| 'accent-focus'
| 'accent-content'
| 'neutral'
| 'neutral-focus'
| 'neutral-content'
| 'info'
| 'info-content'
| 'success'
| 'success-content'
| 'warning'
| 'warning-content'
| 'error'
| 'error-content'
>;

const FONT_COLOR_MAP: { [key in FontColor]: string } = {
'base-content': 'text-base-content',
primary: 'text-primary',
'primary-content': 'text-primary-content',
secondary: 'text-secondary',
'secondary-focus': 'text-secondary-focus',
'secondary-content': 'text-secondary-content',
accent: 'text-accent',
'accent-focus': 'text-accent-focus',
'accent-content': 'text-accent-content',
neutral: 'text-neutral',
'neutral-focus': 'text-neutral-focus',
'neutral-content': 'text-neutral-content',
info: 'text-info',
'info-content': 'text-info-content',
success: 'text-success',
'success-content': 'text-success-content',
warning: 'text-warning',
'warning-content': 'text-warning-content',
error: 'text-error',
'error-content': 'text-error-content',
foreground: 'text-foreground',
'primary-foreground': 'text-primary-foreground',
'secondary-foreground': 'text-secondary-foreground',
'accent-foreground': 'text-accent-foreground',
'destructive-foreground': 'text-destructive-foreground',
'muted-foreground': 'text-muted-foreground',
'popover-foreground': 'text-popover-foreground',
'card-foreground': 'text-card-foreground',
};

const LINE_HEIGHTS = ['none', 'tight', 'snug', 'normal', 'relaxed', 'loose'] as const;
Expand Down Expand Up @@ -98,7 +62,7 @@ export function Typography<C extends ElementType = 'p'>({
as,
size = 'md',
weight = 'normal',
color = 'base-content',
color = 'foreground',
lineHeight = 'normal',
className,
children,
Expand Down
Loading

0 comments on commit aaf0a1e

Please sign in to comment.