Skip to content

Commit

Permalink
feat: add props and css variables for slideshow
Browse files Browse the repository at this point in the history
  • Loading branch information
apledger committed Dec 17, 2024
1 parent b1fcca4 commit a0305c8
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 47 deletions.
16 changes: 16 additions & 0 deletions core/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,20 @@

--accordion-title-font-family: var(--font-family-mono);
--accordion-content-font-family: var(--font-family-body);

/* Slideshow */

--slideshow-focus: hsl(var(--primary));
--slideshow-mask: hsl(var(--foreground) / 80%);
--slideshow-background: color-mix(in oklab, hsl(var(--primary)), black 75%);
--slideshow-title: hsl(var(--background));
--slideshow-title-font-family: var(--font-family-heading);
--slideshow-description: hsl(var(--background) / 80%);
--slideshow-description-font-family: var(--font-family-body);
--slideshow-pagination: hsl(var(--background));
--slideshow-play-border: hsl(var(--contrast-300) / 50%);
--slideshow-play-border-hover: hsl(var(--contrast-300) / 80%);
--slideshow-play-text: hsl(var(--background));
--slideshow-number: hsl(var(--background));
--slideshow-number-font-family: var(--font-family-mono);
}
118 changes: 71 additions & 47 deletions core/vibes/soul/sections/slideshow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,31 @@ import Autoplay from 'embla-carousel-autoplay';
import Fade from 'embla-carousel-fade';
import useEmblaCarousel from 'embla-carousel-react';
import { Pause, Play } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import { ComponentPropsWithoutRef, useCallback, useEffect, useState } from 'react';

import { ButtonLink } from '@/vibes/soul/primitives/button-link';
import { Image } from '~/components/image';

type ButtonLinkProps = ComponentPropsWithoutRef<typeof ButtonLink>;

interface Slide {
title: string;
description?: string;
showDescription?: boolean;
image?: { alt: string; blurDataUrl?: string; src: string };
cta?: { label: string; href: string };
cta?: {
label: string;
href: string;
variant?: ButtonLinkProps['variant'];
size?: ButtonLinkProps['size'];
shape?: ButtonLinkProps['shape'];
};
showCta?: boolean;
}

interface Props {
slides: Slide[];
playOnInit?: boolean;
interval?: number;
className?: string;
}
Expand Down Expand Up @@ -70,9 +81,9 @@ const useProgressButton = (
};
};

export function Slideshow({ slides, interval = 5000, className }: Props) {
export function Slideshow({ slides, playOnInit = true, interval = 5000, className }: Props) {
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true, duration: 20 }, [
Autoplay({ delay: interval }),
Autoplay({ delay: interval, playOnInit }),
Fade(),
]);
const { selectedIndex, scrollSnaps, onProgressButtonClick } = useProgressButton(emblaApi);
Expand Down Expand Up @@ -117,58 +128,71 @@ export function Slideshow({ slides, interval = 5000, className }: Props) {
}, [emblaApi, playCount]);

return (
<section className={clsx('relative h-[80vh] bg-primary-shadow @container', className)}>
<section
className={clsx('relative h-[80vh] bg-[var(--slideshow-background)] @container', className)}
>
<div className="h-full overflow-hidden" ref={emblaRef}>
<div className="flex h-full">
{slides.map(({ title, description, image, cta }, idx) => {
return (
<div className="relative h-full w-full min-w-0 shrink-0 grow-0 basis-full" key={idx}>
<div className="absolute inset-x-0 bottom-0 z-10 bg-gradient-to-t from-foreground/80 to-transparent">
<div className="mx-auto w-full max-w-screen-2xl text-balance px-4 pb-16 pt-12 text-background @xl:px-6 @xl:pb-20 @xl:pt-16 @4xl:px-8 @4xl:pt-20">
<h1 className="m-0 max-w-xl font-heading text-4xl font-medium leading-none @2xl:text-5xl @2xl:leading-[.9] @4xl:text-6xl">
{title}
</h1>
{description != null && description !== '' && (
<p className="mt-2 max-w-xl text-base leading-normal text-background/80 @xl:mt-3 @xl:text-lg">
{description}
</p>
)}
{cta != null && cta.href !== '' && cta.label !== '' && (
<ButtonLink className="mt-6 @xl:mt-8" href={cta.href} variant="tertiary">
{cta.label}
</ButtonLink>
)}
{slides.map(
({ title, description, showDescription = true, image, cta, showCta = true }, idx) => {
return (
<div
className="relative h-full w-full min-w-0 shrink-0 grow-0 basis-full"
key={idx}
>
<div className="absolute inset-x-0 bottom-0 z-10 bg-gradient-to-t from-[var(--slideshow-mask)] to-transparent">
<div className="mx-auto w-full max-w-screen-2xl text-balance px-4 pb-16 pt-12 @xl:px-6 @xl:pb-20 @xl:pt-16 @4xl:px-8 @4xl:pt-20">
<h1 className="m-0 max-w-xl font-[family-name:var(--slideshow-title-font-family)] text-4xl font-medium leading-none text-[var(--slideshow-title)] @2xl:text-5xl @2xl:leading-[.9] @4xl:text-6xl">
{title}
</h1>
{showDescription && (
<p className="max-w-x mt-2 font-[family-name:var(--slideshow-description-font-family)] text-base leading-normal text-[var(--slideshow-description)] @xl:mt-3 @xl:text-lg">
{description}
</p>
)}
{showCta && (
<ButtonLink
className="mt-6 @xl:mt-8"
href={cta?.href ?? '#'}
shape={cta?.shape ?? 'pill'}
size={cta?.size ?? 'large'}
variant={cta?.variant ?? 'tertiary'}
>
{cta?.label ?? 'Learn more'}
</ButtonLink>
)}
</div>
</div>
</div>

{image?.src != null && image.src !== '' && (
<Image
alt={image.alt}
blurDataURL={image.blurDataUrl}
className="block h-20 w-full object-cover"
fill
placeholder={
image.blurDataUrl != null && image.blurDataUrl !== '' ? 'blur' : 'empty'
}
priority
sizes="100vw"
src={image.src}
/>
)}
</div>
);
})}
{image?.src != null && image.src !== '' && (
<Image
alt={image.alt}
blurDataURL={image.blurDataUrl}
className="block h-20 w-full object-cover"
fill
placeholder={
image.blurDataUrl != null && image.blurDataUrl !== '' ? 'blur' : 'empty'
}
priority
sizes="100vw"
src={image.src}
/>
)}
</div>
);
},
)}
</div>
</div>

{/* Controls */}
<div className="absolute bottom-4 left-1/2 flex w-full max-w-screen-2xl -translate-x-1/2 flex-wrap items-center px-4 text-background @xl:bottom-6 @xl:px-6 @4xl:px-8">
<div className="absolute bottom-4 left-1/2 flex w-full max-w-screen-2xl -translate-x-1/2 flex-wrap items-center px-4 @xl:bottom-6 @xl:px-6 @4xl:px-8">
{/* Progress Buttons */}
{scrollSnaps.map((_: number, index: number) => {
return (
<button
aria-label={`View image number ${index + 1}`}
className="rounded-lg px-1.5 py-2 focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-primary"
className="rounded-lg px-1.5 py-2 focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-[var(--slideshow-focus)]"
key={index}
onClick={() => {
onProgressButtonClick(index);
Expand All @@ -179,7 +203,7 @@ export function Slideshow({ slides, interval = 5000, className }: Props) {
{/* White Bar - Current Index Indicator / Progress Bar */}
<div
className={clsx(
'absolute h-0.5 bg-background',
'absolute h-0.5 bg-[var(--slideshow-pagination)]',
'opacity-0 fill-mode-forwards',
isPlaying ? 'running' : 'paused',
index === selectedIndex
Expand All @@ -194,7 +218,7 @@ export function Slideshow({ slides, interval = 5000, className }: Props) {
/>
{/* Grey Bar BG */}
<div
className="h-0.5 bg-background opacity-30"
className="h-0.5 bg-[var(--slideshow-pagination)] opacity-30"
style={{ width: `${150 / slides.length}px` }}
/>
</div>
Expand All @@ -203,15 +227,15 @@ export function Slideshow({ slides, interval = 5000, className }: Props) {
})}

{/* Carousel Count - "01/03" */}
<span className="ml-auto mr-3 mt-px font-mono text-sm">
<span className="ml-auto mr-3 mt-px font-[family-name:var(--slideshow-number-font-family)] text-sm text-[var(--slideshow-number)]">
{selectedIndex + 1 < 10 ? `0${selectedIndex + 1}` : selectedIndex + 1}/
{slides.length < 10 ? `0${slides.length}` : slides.length}
</span>

{/* Stop / Start Button */}
<button
aria-label={isPlaying ? 'Pause' : 'Play'}
className="flex h-7 w-7 items-center justify-center rounded-lg border border-contrast-300/50 ring-primary transition-opacity duration-300 hover:border-contrast-300/80 focus-visible:outline-0 focus-visible:ring-2"
className="flex h-7 w-7 items-center justify-center rounded-lg border border-[var(--slideshow-play-border)] text-[var(--slideshow-play-text)] ring-[var(--slideshow-focus)] transition-opacity duration-300 hover:border-[var(--slideshow-play-border-hover)] focus-visible:outline-0 focus-visible:ring-2"
onClick={toggleAutoplay}
type="button"
>
Expand Down

0 comments on commit a0305c8

Please sign in to comment.