Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(SLB-455): add subgrid to card layout #357

Open
wants to merge 18 commits into
base: release
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/drupal/gutenberg_blocks/js/blocks/teaser-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ registerBlockType<{
attributes: {
layout: {
type: 'string',
default: 'GRID',
default: '',
},
buttonText: {
type: 'string',
Expand Down
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ 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="p-5">
<div className="grid grid-rows-[auto_1fr_auto] gap-4 p-5">
<h5
id={formattedID}
className="mb-2 text-2xl font-bold tracking-tight text-gray-900"
Expand All @@ -28,7 +28,7 @@ export const CardItem = ({
{hero?.headline ? <div>{hero?.headline}</div> : 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 All @@ -53,11 +53,14 @@ export const CardItem = ({
</svg>
</Link>
</div>
<div className="rounded-t-lg">
<div className="row-start-1">
{teaserImage ? (
<Image {...teaserImage} className="w-full" />
<Image
{...teaserImage}
className="aspect-[16/9] w-full rounded-t-lg"
/>
) : (
<div className="aspect-[4/3] bg-indigo-200" />
<div className="aspect-[16/9] rounded-t-lg bg-indigo-200" />
)}
</div>
</article>
Expand Down
90 changes: 90 additions & 0 deletions packages/ui/src/components/Organisms/Carousel/Carousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
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}>
<ul
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) => (
<li
key={index}
className="embla__slide grid min-w-0 grid-cols-subgrid p-2"
>
{child}
</li>
))}
</ul>
</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>
);
};
6 changes: 3 additions & 3 deletions 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 Expand Up @@ -50,10 +50,10 @@ 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}>
<li key={item.path} className="grid grid-rows-subgrid">
<CardItem {...item} />
</li>
);
Expand Down
Loading
Loading