Skip to content
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
1 change: 1 addition & 0 deletions packages/unity-react-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
},
"dependencies": {
"@glidejs/glide": "^3.6.0",
"embla-carousel-react": "^8.6.0",
"react-share": "^5.3"
},
"peerDependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//@ts-check
import React, { useState, useEffect, useCallback } from "react";
import useEmblaCarousel from "embla-carousel-react";
import PropTypes from "prop-types";

const EmblaImageGalleryPrototype = ({
imageItems = [],
hasContent = false,
maxWidth = "996px"
}) => {
//Initializing Embla
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: false, align: "start" });

//React state to track the active slide
const [selectedIndex, setSelectedIndex] = useState(0);

//Navigation controls
const scrollPrev = useCallback(() => emblaApi && emblaApi.scrollPrev(), [emblaApi]);
const scrollNext = useCallback(() => emblaApi && emblaApi.scrollNext(), [emblaApi]);
const scrollTo = useCallback((index) => emblaApi && emblaApi.scrollTo(index), [emblaApi]);

const onSelect = useCallback(() => {
if (!emblaApi) return;
setSelectedIndex(emblaApi.selectedScrollSnap()); //save number to react state
}, [emblaApi]);

//Use direct react callback to update uI
useEffect(() => {
if (!emblaApi) return;
emblaApi.on("select", onSelect);
emblaApi.on("reInit", onSelect);
onSelect(); //Set initial state on load
}, [emblaApi, onSelect]);

if (!imageItems || imageItems.length === 0) return null; //prevent website from crashing if empty data

const currentItem = imageItems[selectedIndex];

return (
//600px so it matches storybook wrapper limit (temperory)
<div className="embla-image-gallery" style={{ maxWidth, margin: "auto", width: "100%", height: "600px", display: "flex", flexDirection: "column" }}>

<div className="embla__viewport" ref={emblaRef} style={{ flex: "1 1 0px", minHeight: 0, overflow: "hidden" }}> {/* force images to shrink and fit */}

<div className="embla__container" style={{ display: "flex", height: "100%" }}>
{imageItems.map((item) => ( //iterate and render every image inside contatiner
<div
className="embla__slide"
key={item.id}
style={{ flex: "0 0 100%", minWidth: 0, height: "100%" }}
>
<img
src={item.imageSource}
alt={item.imageAltText || ""}
loading="lazy"
decoding="async"
style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }}
/>
</div>
))}
</div>
</div>

{/* prevent nav bar from getting reduced in size */}
<div className="image-gallery-action-area" style={{ flexShrink: 0, border: "1px solid #d0d0d0", borderTop: 0 }}>

{/* Arrows */}
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "1rem" }}>

<button
onClick={scrollPrev}
disabled={!emblaApi?.canScrollPrev()} //disable left side if first image
className="glide__arrow"
style={{ width: "40px", height: "40px" }}
>
&#8592;
</button>

{/* thumbnails in nav bar */}
<div className="embla-thumbnails" style={{ display: "flex", gap: "16px", overflowX: "auto" }}> {/* allow scrolling images if too many */}
{/* clickable thumbnail images */}
{imageItems.map((item, index) => (
<button
key={index}
onClick={() => scrollTo(index)}
aria-label={`Go to slide ${index + 1}`}
style={{
padding: 0,
cursor: "pointer",
border: selectedIndex === index ? "4px solid #ffc627" : "2px solid transparent",
outline: "none"
}}
>
<img
src={item.imageSource}
alt=""
style={{ width: "88px", height: "48px", objectFit: "cover", display: "block" }}
/>
</button>
))}
</div>

<button
onClick={scrollNext}
disabled={!emblaApi?.canScrollNext()} //disable button if last image
className="glide__arrow"
style={{ width: "40px", height: "40px" }}
>
&#8594;
</button>
</div>

{/* Captions */}
{hasContent && (currentItem.title || currentItem.content) && ( //hide if no text caption
<figcaption className="figure-caption uds-figure-caption" style={{ padding: "0 1rem 1rem" }}>
<div className="uds-caption-text">
{currentItem.title && <h2>{currentItem.title}</h2>}
<div dangerouslySetInnerHTML={{ __html: currentItem.content }} />
</div>
</figcaption>
)}
</div>
</div>
);
};

EmblaImageGalleryPrototype.propTypes = {
imageItems: PropTypes.array.isRequired,
hasContent: PropTypes.bool,
maxWidth: PropTypes.string,
};

export { EmblaImageGalleryPrototype };

Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,15 @@ export const ImageCarouselWithMoreContent = () => (
/>
</Wrapper>
);

import { EmblaImageGalleryPrototype } from "./EmblaImageGalleryPrototype";

export const EmblaPrototype = () => (
<Wrapper>
<EmblaImageGalleryPrototype
maxWidth={maxWidth}
imageItems={mockItemWithContent()}
hasContent
/>
</Wrapper>
);
29 changes: 29 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@ __metadata:
chromium-bidi: "npm:^3.0.0"
classnames: "npm:^2.2.6"
css-loader: "npm:^5.2.0"
embla-carousel-react: "npm:^8.6.0"
file-loader: "npm:^6.2.0"
glob: "npm:^7.1.6"
html-entities: "npm:^2.3.1"
Expand Down Expand Up @@ -14359,6 +14360,34 @@ __metadata:
languageName: node
linkType: hard

"embla-carousel-react@npm:^8.6.0":
version: 8.6.0
resolution: "embla-carousel-react@npm:8.6.0"
dependencies:
embla-carousel: "npm:8.6.0"
embla-carousel-reactive-utils: "npm:8.6.0"
peerDependencies:
react: ^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
checksum: 10c0/461b0c427a6fca62e730c7b39d08e9c55b01a2138c173d5b9ebf77ec92092a900b45ac384731c77382b48d5d65946994d3abba6e9899a66d6e16f8ddf191ed1c
languageName: node
linkType: hard

"embla-carousel-reactive-utils@npm:8.6.0":
version: 8.6.0
resolution: "embla-carousel-reactive-utils@npm:8.6.0"
peerDependencies:
embla-carousel: 8.6.0
checksum: 10c0/33d07e6df4d8dec9b4fc95858838ea2d5e5df078039c05b2a342c05ba4979d8d18564a1c607da445fed744daf969a0b5a90124ce487a854e86ced6f0458b51e9
languageName: node
linkType: hard

"embla-carousel@npm:8.6.0":
version: 8.6.0
resolution: "embla-carousel@npm:8.6.0"
checksum: 10c0/f4c598e7be28b70340d31ffd2bebb2472db370b0c81d9b089bf9555cf618695f35dc4a0694565c994c9ab972731123063f945aa09ff485df0df761d79c6a08ef
languageName: node
linkType: hard

"emittery@npm:^0.13.1":
version: 0.13.1
resolution: "emittery@npm:0.13.1"
Expand Down