Skip to content

Commit

Permalink
Merge branch 'SLB-455-card-subgrid' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
dan2k3k4 committed Jan 22, 2025
2 parents eb9685e + ae75bbe commit 5c648aa
Show file tree
Hide file tree
Showing 11 changed files with 421 additions and 76 deletions.
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 @@ -448,7 +448,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 @@ -47,6 +47,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 @@ -94,6 +96,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
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const CardItem = ({
return (
<article
aria-labelledby={formattedID}
className="relative flex max-w-sm flex-col-reverse overflow-hidden rounded-lg bg-white focus-within:outline focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-indigo-600 hover:shadow"
className="relative grid max-w-sm grid-rows-[auto_1fr] rounded-lg bg-white focus-within:outline focus-within:outline-2 focus-within:outline-indigo-600 hover:shadow"
>
<div className="grid grid-rows-[auto_1fr_auto] gap-4 p-5">
<h5
Expand All @@ -41,7 +41,7 @@ export const CardItem = ({
) : null}
<Link
href={path}
className="inline-flex items-center rounded-lg border border-blue-700 px-3 py-2 text-center text-sm font-medium text-blue-700 after:absolute after:inset-0 after:content-[''] hover:bg-blue-800 hover:text-white focus:outline-offset-4"
className="row-start-3 inline-flex items-center justify-self-start rounded-lg border border-blue-700 px-3 py-2 text-center text-sm font-medium text-blue-700 after:absolute after:inset-0 after:content-[''] hover:bg-blue-800 hover:text-white focus:outline-offset-4"
>
<span className="sr-only size-0 overflow-hidden">{title}</span>
{readMoreText ||
Expand Down
87 changes: 87 additions & 0 deletions packages/ui/src/components/Organisms/Carousel/Carousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import clsx from 'clsx';
import { EmblaOptionsType } from 'embla-carousel';
import useEmblaCarousel from 'embla-carousel-react';
import React, { ReactNode, useEffect } from 'react';

import {
NextButton,
PrevButton,
usePrevNextButtons,
} from './CarouselArrowButtons';
import { DotButton, useDotButton } from './CarouselDotButton';

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 touch-pan-y touch-pinch-zoom grid-flow-col gap-4',
{
'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 mt-4 grid grid-cols-[auto_1fr] justify-between gap-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 align-center flex flex-wrap justify-end gap-2">
{scrollSnaps.map((_: unknown, index: number) => (
<DotButton
key={index}
onClick={() => onDotButtonClick(index)}
className={clsx(
'embla__dot inline-flex h-6 w-6 cursor-pointer touch-manipulation 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 { EmblaCarouselType } from 'embla-carousel';
import React, {
ComponentPropsWithRef,
useCallback,
useEffect,
useState,
} from 'react';

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 inline-flex h-8 w-8 cursor-pointer touch-manipulation items-center justify-center text-gray-400"
type="button"
{...restProps}
>
<svg className="embla__button__svg h-[35%] w-[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 inline-flex h-8 w-8 cursor-pointer touch-manipulation items-center justify-center text-gray-400"
type="button"
{...restProps}
>
<svg className="embla__button__svg h-[35%] w-[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 { EmblaCarouselType } from 'embla-carousel';
import React, {
ComponentPropsWithRef,
useCallback,
useEffect,
useState,
} from 'react';

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>
);
};
4 changes: 2 additions & 2 deletions packages/ui/src/components/Organisms/ContentHub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Alert } from '../Molecules/Alert';
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 Expand Up @@ -73,7 +73,7 @@ export function ContentHub({ pageSize = 10 }: { pageSize: number }) {
) : null}
{data?.contentHub.total ? (
<>
<ul className="my-8 grid gap-4 md:grid-cols-2 lg:grid-cols-3 lg:gap-8">
<ul className="my-8 grid auto-rows-fr grid-rows-subgrid gap-4 md:grid-cols-2 lg:grid-cols-3 lg:gap-8">
{data?.contentHub.items.filter(isTruthy).map((item) => {
return (
<li key={item.path} className="grid grid-rows-subgrid">
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 5c648aa

Please sign in to comment.