Skip to content

Commit

Permalink
chore: add first initial version of working carousel
Browse files Browse the repository at this point in the history
  • Loading branch information
dan2k3k4 committed Jan 22, 2025
1 parent 67a593c commit f2accca
Show file tree
Hide file tree
Showing 12 changed files with 8,099 additions and 30,808 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
},
"private": true,
"devDependencies": {
"@commitlint/cli": "^18.4.3",
"@commitlint/config-conventional": "^18.4.3",
"@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.3",
"husky": "^8.0.3",
"prettier": "^3.2.5",
"turbo": "^2.0.6"
"prettier": "^3.4.2",
"turbo": "^2.3.3"
},
"resolutions": {
"gatsby-plugin-sharp": "5.13.1",
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion packages/schema/src/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ type BlockTeaserListFilters {
}

"""
Inteface for anything that can appear as a card (teaser) item
Interface for anything that can appear as a card (teaser) item
"""
interface CardItem @resolveEntityBundle {
id: ID!
Expand Down
3 changes: 3 additions & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
"@heroicons/react": "^2.1.1",
"@hookform/resolvers": "^3.3.3",
"clsx": "^2.1.0",
"embla-carousel-class-names": "^8.5.2",
"embla-carousel-react": "^8.5.2",
"framer-motion": "^10.17.4",
"hast-util-is-element": "^2.1.3",
"hast-util-select": "^5.0.5",
Expand Down Expand Up @@ -93,6 +95,7 @@
"autoprefixer": "^10.4.16",
"axe-playwright": "^2.0.1",
"cssnano": "^6.0.3",
"embla-carousel": "^8.5.2",
"happy-dom": "^12.10.3",
"nyc": "^15.1.0",
"postcss": "^8.4.32",
Expand Down
91 changes: 91 additions & 0 deletions packages/ui/src/components/Organisms/Carousel/Carousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React, {
ReactComponentElement,
ReactElement,
ReactNode,
useEffect,
} from 'react';
import { DotButton, useDotButton } from './CarouselDotButton';
import {
PrevButton,
NextButton,
usePrevNextButtons,
} from './CarouselArrowButtons';
import useEmblaCarousel from 'embla-carousel-react';
import { EmblaOptionsType } from 'embla-carousel';
import clsx from 'clsx';

export function Carousel({
children,
options,
visibleSlides = 2,
}: {
children: ReactNode;
options: EmblaOptionsType;
visibleSlides?: number;
}) {
const [emblaRef, emblaApi] = useEmblaCarousel(options);

const { selectedIndex, scrollSnaps, onDotButtonClick } =
useDotButton(emblaApi);

const {
prevBtnDisabled,
nextBtnDisabled,
onPrevButtonClick,
onNextButtonClick,
} = usePrevNextButtons(emblaApi);

useEffect(() => {
if (emblaApi) {
// Do we want to use emblaApi for anything?
// console.log(emblaApi.slideNodes());
}
}, [emblaApi]);

return (
<div className="embla m-auto max-w-full">
<div className="embla__viewport overflow-hidden" ref={emblaRef}>
<div
className={clsx(
'embla__container grid grid-flow-col gap-4 touch-pan-y touch-pinch-zoom',
{
'auto-cols-[100%]': visibleSlides === 1,
'auto-cols-[50%]': visibleSlides === 2,
'auto-cols-[33%]': visibleSlides === 3,
'auto-cols-[25%]': visibleSlides === 4,
},
)}
>
{React.Children.map(children, (child, index) => (
<div key={index} className="embla__slide min-w-0 p-2">
{child}
</div>
))}
</div>
</div>

<div className="embla__controls grid grid-cols-[auto_1fr] justify-between gap-4 mt-4">
<div className="embla__buttons grid grid-cols-[repeat(2,1fr)] gap-2">
<PrevButton onClick={onPrevButtonClick} disabled={prevBtnDisabled} />
<NextButton onClick={onNextButtonClick} disabled={nextBtnDisabled} />
</div>

<div className="embla__dots flex flex-wrap gap-2 justify-end align-center">
{scrollSnaps.map((_: unknown, index: number) => (
<DotButton
key={index}
onClick={() => onDotButtonClick(index)}
className={clsx(
'embla__dot touch-manipulation cursor-pointer w-6 h-6 inline-flex items-center justify-center rounded-[50%] text-gray-400',
{
'embla__dot--selected bg-gray-800': index === selectedIndex,
'bg-gray-200': index !== selectedIndex,
},
)}
/>
))}
</div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import React, {
ComponentPropsWithRef,
useCallback,
useEffect,
useState,
} from 'react';
import { EmblaCarouselType } from 'embla-carousel';

type UsePrevNextButtonsType = {
prevBtnDisabled: boolean;
nextBtnDisabled: boolean;
onPrevButtonClick: () => void;
onNextButtonClick: () => void;
};

export const usePrevNextButtons = (
emblaApi: EmblaCarouselType | undefined,
): UsePrevNextButtonsType => {
const [prevBtnDisabled, setPrevBtnDisabled] = useState(true);
const [nextBtnDisabled, setNextBtnDisabled] = useState(true);

const onPrevButtonClick = useCallback(() => {
if (!emblaApi) return;
emblaApi.scrollPrev();
}, [emblaApi]);

const onNextButtonClick = useCallback(() => {
if (!emblaApi) return;
emblaApi.scrollNext();
}, [emblaApi]);

const onSelect = useCallback((emblaApi: EmblaCarouselType) => {
setPrevBtnDisabled(!emblaApi.canScrollPrev());
setNextBtnDisabled(!emblaApi.canScrollNext());
}, []);

useEffect(() => {
if (!emblaApi) return;

onSelect(emblaApi);
emblaApi.on('reInit', onSelect).on('select', onSelect);
}, [emblaApi, onSelect]);

return {
prevBtnDisabled,
nextBtnDisabled,
onPrevButtonClick,
onNextButtonClick,
};
};

type PropType = ComponentPropsWithRef<'button'>;

export const PrevButton: React.FC<PropType> = (props) => {
const { children, ...restProps } = props;

return (
<button
className="embla__button embla__button--prev touch-manipulation inline-flex cursor-pointer items-center justify-center w-8 h-8 text-gray-400"
type="button"
{...restProps}
>
<svg className="embla__button__svg w-[35%] h-[35%]" viewBox="0 0 532 532">
<path
fill="currentColor"
d="M355.66 11.354c13.793-13.805 36.208-13.805 50.001 0 13.785 13.804 13.785 36.238 0 50.034L201.22 266l204.442 204.61c13.785 13.805 13.785 36.239 0 50.044-13.793 13.796-36.208 13.796-50.002 0a5994246.277 5994246.277 0 0 0-229.332-229.454 35.065 35.065 0 0 1-10.326-25.126c0-9.2 3.393-18.26 10.326-25.2C172.192 194.973 332.731 34.31 355.66 11.354Z"
/>
</svg>
{children}
</button>
);
};

export const NextButton: React.FC<PropType> = (props) => {
const { children, ...restProps } = props;

return (
<button
className="embla__button embla__button--next touch-manipulation inline-flex cursor-pointer items-center justify-center w-8 h-8 text-gray-400"
type="button"
{...restProps}
>
<svg className="embla__button__svg w-[35%] h-[35%]" viewBox="0 0 532 532">
<path
fill="currentColor"
d="M176.34 520.646c-13.793 13.805-36.208 13.805-50.001 0-13.785-13.804-13.785-36.238 0-50.034L330.78 266 126.34 61.391c-13.785-13.805-13.785-36.239 0-50.044 13.793-13.796 36.208-13.796 50.002 0 22.928 22.947 206.395 206.507 229.332 229.454a35.065 35.065 0 0 1 10.326 25.126c0 9.2-3.393 18.26-10.326 25.2-45.865 45.901-206.404 206.564-229.332 229.52Z"
/>
</svg>
{children}
</button>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, {
ComponentPropsWithRef,
useCallback,
useEffect,
useState,
} from 'react';
import { EmblaCarouselType } from 'embla-carousel';

type UseDotButtonType = {
selectedIndex: number;
scrollSnaps: number[];
onDotButtonClick: (index: number) => void;
};

export const useDotButton = (
emblaApi: EmblaCarouselType | undefined,
): UseDotButtonType => {
const [selectedIndex, setSelectedIndex] = useState(0);
const [scrollSnaps, setScrollSnaps] = useState<number[]>([]);

const onDotButtonClick = useCallback(
(index: number) => {
if (!emblaApi) return;
emblaApi.scrollTo(index);
},
[emblaApi],
);

const onInit = useCallback((emblaApi: EmblaCarouselType) => {
setScrollSnaps(emblaApi.scrollSnapList());
}, []);

const onSelect = useCallback((emblaApi: EmblaCarouselType) => {
setSelectedIndex(emblaApi.selectedScrollSnap());
}, []);

useEffect(() => {
if (!emblaApi) return;

onInit(emblaApi);
onSelect(emblaApi);
emblaApi.on('reInit', onInit).on('reInit', onSelect).on('select', onSelect);
}, [emblaApi, onInit, onSelect]);

return {
selectedIndex,
scrollSnaps,
onDotButtonClick,
};
};

type PropType = ComponentPropsWithRef<'button'>;

export const DotButton: React.FC<PropType> = (props) => {
const { children, ...restProps } = props;

return (
<button type="button" {...restProps}>
{children}
</button>
);
};
2 changes: 1 addition & 1 deletion packages/ui/src/components/Organisms/ContentHub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useOperation } from '../../utils/operation';
import { Pagination, useCurrentPage } from '../Molecules/Pagination';
import { SearchForm, useSearchParameters } from '../Molecules/SearchForm';
import { Loading } from '../Routes/Loading';
import { CardItem } from './Card';
import { CardItem } from './CardItem';

export type ContentHubQueryArgs = {
title: string | undefined;
Expand Down
74 changes: 36 additions & 38 deletions packages/ui/src/components/Organisms/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,44 +135,42 @@ export function Footer() {
aria-label="Footer Primary"
>
{items.map((item, key) => (
<>
<li
key={key + 'header'}
className={
'mb-3 block w-1/2 max-w-44 pr-5 last:pr-0 md:mb-0 md:text-left lg:w-44 lg:pr-8'
}
>
{item.target ? (
<Link
href={item.target!}
className={
'mb-4 block text-[0.875rem] font-semibold uppercase leading-[1.313rem] text-gray-900 transition-all hover:underline'
}
>
{item.title}
</Link>
) : (
<span
className={
'mb-4 block text-[0.875rem] font-semibold uppercase leading-[1.313rem] transition-all'
}
>
{item.title}
</span>
)}
{item.children.length > 0
? item.children.map((child) => (
<Link
key={child.target}
href={child.target}
className="mb-4 block text-base font-normal text-gray-500 transition-all hover:underline"
>
{child.title}
</Link>
))
: null}
</li>
</>
<li
key={key + 'header'}
className={
'mb-3 block w-1/2 max-w-44 pr-5 last:pr-0 md:mb-0 md:text-left lg:w-44 lg:pr-8'
}
>
{item.target ? (
<Link
href={item.target!}
className={
'mb-4 block text-[0.875rem] font-semibold uppercase leading-[1.313rem] text-gray-900 transition-all hover:underline'
}
>
{item.title}
</Link>
) : (
<span
className={
'mb-4 block text-[0.875rem] font-semibold uppercase leading-[1.313rem] transition-all'
}
>
{item.title}
</span>
)}
{item.children.length > 0
? item.children.map((child) => (
<Link
key={child.target}
href={child.target}
className="mb-4 block text-base font-normal text-gray-500 transition-all hover:underline"
>
{child.title}
</Link>
))
: null}
</li>
))}
</ul>
</nav>
Expand Down
Loading

0 comments on commit f2accca

Please sign in to comment.