diff --git a/packages/unity-bootstrap-theme/src/scss/extends/_images.scss b/packages/unity-bootstrap-theme/src/scss/extends/_images.scss
index 337f2eea61..b600e61d51 100644
--- a/packages/unity-bootstrap-theme/src/scss/extends/_images.scss
+++ b/packages/unity-bootstrap-theme/src/scss/extends/_images.scss
@@ -23,6 +23,7 @@
// Images with captions
.uds-figure {
+ margin: 0;
width: 100%;
img.img-original {
width: initial;
@@ -60,8 +61,14 @@
&.uds-img-drop-shadow {
box-shadow: $uds-size-spacing-0 $uds-size-spacing-1 $uds-size-spacing-2
rgba(25, 25, 25, 0.2);
- .uds-figure {
- margin-bottom: $uds-size-spacing-0;
+ }
+}
+
+.uds-card-arrangement {
+ .uds-img {
+ img {
+ aspect-ratio: 4/3;
+ object-fit: cover;
}
}
}
diff --git a/packages/unity-react-core/src/components/Card/Card.jsx b/packages/unity-react-core/src/components/Card/Card.jsx
index 990516f3fe..6e5a7812ea 100644
--- a/packages/unity-react-core/src/components/Card/Card.jsx
+++ b/packages/unity-react-core/src/components/Card/Card.jsx
@@ -46,7 +46,69 @@ export const Card = ({
tags,
showBorders = true,
cardLink,
+ cards = [],
+ columns = "0",
+ layout = "auto"
}) => {
+ // If multiple cards are provided, render them in a card container
+ if (cards.length > 1) {
+ const getColumnClass = () => {
+ switch (columns) {
+ case "2":
+ return "";
+ case "3":
+ return "three-columns";
+ case "4":
+ return "four-columns";
+ default:
+ return "";
+ }
+ };
+
+ return (
+
+
+ {cards.map((card, index) => (
+
+ ))}
+
+
+ );
+ }
+
+ // Single card - render as before
return (
(
+
+);
+
+export const MultipleCardsTwoColumns = MultipleCardsTemplate.bind({});
+MultipleCardsTwoColumns.args = {
+ columns: "2",
+ cards: [
+ {
+ title: "First Card",
+ body: "This is the first card in a two-column layout.",
+ image: img1,
+ imageAltText: "First card image",
+ type: "default",
+ },
+ {
+ title: "Second Card",
+ body: "This is the second card in a two-column layout.",
+ image: img1,
+ imageAltText: "Second card image",
+ type: "default",
+ },
+ {
+ title: "Third Card",
+ body: "This is the third card in a two-column layout.",
+ image: img1,
+ imageAltText: "Third card image",
+ type: "default",
+ },
+ {
+ title: "Fourth Card",
+ body: "This is the fourth card in a two-column layout.",
+ image: img1,
+ imageAltText: "Fourth card image",
+ type: "default",
+ },
+ ],
+};
+
+export const MultipleCardsFourColumns = MultipleCardsTemplate.bind({});
+MultipleCardsFourColumns.args = {
+ columns: "4",
+ cards: [
+ {
+ title: "First Card",
+ body: "This is the first card in a four-column layout.",
+ image: img1,
+ imageAltText: "First card image",
+ type: "default",
+ },
+ {
+ title: "Second Card",
+ body: "This is the second card in a four-column layout.",
+ image: img1,
+ imageAltText: "Second card image",
+ type: "default",
+ },
+ {
+ title: "Third Card",
+ body: "This is the third card in a four-column layout.",
+ image: img1,
+ imageAltText: "Third card image",
+ type: "default",
+ },
+ {
+ title: "Fourth Card",
+ body: "This is the fourth card in a four-column layout.",
+ image: img1,
+ imageAltText: "Fourth card image",
+ type: "default",
+ },
+ ],
+};
+
+export const MultipleCardsThreeColumns = MultipleCardsTemplate.bind({});
+MultipleCardsThreeColumns.args = {
+ columns: "3",
+ cards: [
+ {
+ title: "First Card",
+ body: "This is the first card in a three-column layout.",
+ image: img1,
+ imageAltText: "First card image",
+ type: "default",
+ },
+ {
+ title: "Second Card",
+ body: "This is the second card in a three-column layout.",
+ image: img1,
+ imageAltText: "Second card image",
+ type: "default",
+ },
+ {
+ title: "Third Card",
+ body: "This is the third card in a three-column layout.",
+ image: img1,
+ imageAltText: "Third card image",
+ type: "default",
+ },
+ {
+ title: "Fourth Card",
+ body: "This is the fourth card in a three-column layout.",
+ image: img1,
+ imageAltText: "Fourth card image",
+ type: "default",
+ },
+ {
+ title: "Fifth Card",
+ body: "This is the fifth card in a three-column layout.",
+ image: img1,
+ imageAltText: "Fifth card image",
+ type: "default",
+ },
+ {
+ title: "Sixth Card",
+ body: "This is the sixth card in a three-column layout.",
+ image: img1,
+ imageAltText: "Sixth card image",
+ type: "default",
+ },
+ ],
+};
diff --git a/packages/unity-react-core/src/components/Card/Card.test.jsx b/packages/unity-react-core/src/components/Card/Card.test.jsx
index f9337c26f3..0c8056c454 100644
--- a/packages/unity-react-core/src/components/Card/Card.test.jsx
+++ b/packages/unity-react-core/src/components/Card/Card.test.jsx
@@ -1,7 +1,7 @@
// @ts-check
import { render, cleanup } from "@testing-library/react";
import React from "react";
-import { expect, describe, it, afterEach, beforeEach, test } from "vitest";
+import { expect, describe, it, afterEach, beforeEach, test, vi } from "vitest";
import { Card } from "./Card";
@@ -100,4 +100,58 @@ describe("#Card options", () => {
className
);
});
+
+ describe("Multiple Cards", () => {
+ const multipleCardsArgs = {
+ columns: "2",
+ cards: [
+ {
+ title: "First Card",
+ body: "First card body text",
+ image: "https://picsum.photos/300/200",
+ imageAltText: "First card image",
+ },
+ {
+ title: "Second Card",
+ body: "Second card body text",
+ image: "https://picsum.photos/300/200",
+ imageAltText: "Second card image",
+ },
+ ],
+ };
+
+ beforeEach(() => {
+ component = renderCard(multipleCardsArgs);
+ });
+
+ it("should render multiple cards container", () => {
+ const parentContainer = component.container.querySelector(
+ ".uds-card-arrangement"
+ );
+ const cardContainer = component.container.querySelector(
+ ".uds-card-arrangement-card-container"
+ );
+ vi.waitFor(() => {
+ expect(parentContainer).toBeInTheDocument();
+ expect(cardContainer).toBeInTheDocument();
+ });
+ });
+
+ it("should render correct number of cards", () => {
+ const cards = component.container.querySelectorAll(".card");
+ expect(cards).toHaveLength(2);
+ });
+
+ it("should apply auto-arrangement class", () => {
+ const container = component.container.querySelector(".auto-arrangement");
+ expect(container).toBeInTheDocument();
+ });
+
+ it("should apply four-columns class when columns is 4", () => {
+ const fourColumnArgs = { ...multipleCardsArgs, columns: "4" };
+ component = renderCard(fourColumnArgs);
+ const container = component.container.querySelector(".four-columns");
+ expect(container).toBeInTheDocument();
+ });
+ });
});
diff --git a/packages/unity-react-core/src/components/Image/Image.jsx b/packages/unity-react-core/src/components/Image/Image.jsx
index 485094821c..5693f479ad 100644
--- a/packages/unity-react-core/src/components/Image/Image.jsx
+++ b/packages/unity-react-core/src/components/Image/Image.jsx
@@ -7,6 +7,7 @@ import React from "react";
/**
* @typedef {import('../../core/types/image-types').ImageComponentProps} ImageComponentProps
+ * @typedef {import('../../core/types/image-types').ImageItemProps} ImageItemProps
*/
/**
@@ -14,11 +15,11 @@ import React from "react";
*/
/**
- * @param {ImageComponentProps} props
+ * Base Image component for rendering individual images
+ * @param {ImageItemProps} props
* @returns {JSX.Element}
*/
-
-export const Image = ({
+const BaseImage = ({
src,
alt,
cssClasses,
@@ -89,15 +90,80 @@ export const Image = ({
);
};
+/**
+ * @param {ImageComponentProps} props
+ * @returns {JSX.Element}
+ */
+
+export const Image = ({
+ src,
+ alt,
+ cssClasses,
+ loading = "lazy",
+ decoding = "async",
+ dataTestId,
+ fetchPriority = "auto",
+ width,
+ height,
+ cardLink,
+ title,
+ caption,
+ captionTitle,
+ border,
+ dropShadow,
+ images,
+ columns,
+}) => {
+ // If images array is provided, render multiple images
+ if (images && Array.isArray(images) && images.length > 0) {
+ const containerClasses = classNames("uds-card-arrangement-card-container", {
+ "auto-arrangement": !columns || columns === "0",
+ "three-columns": columns === "3",
+ "four-columns": columns === "4",
+ });
+
+ return (
+
+
+ {images.map((imageItem, index) => (
+
+ ))}
+
+
+ );
+ }
+
+ // Otherwise render single image (backward compatibility)
+ return (
+
+ );
+};
+
Image.propTypes = {
/**
* Image source (We keep the same name as in the whole project)
*/
- src: PropTypes.string.isRequired,
+ src: PropTypes.string,
/**
* Image alt text
*/
- alt: PropTypes.string.isRequired,
+ alt: PropTypes.string,
/**
* Array classes for the image
*/
@@ -129,4 +195,30 @@ Image.propTypes = {
captionTitle: PropTypes.string,
border: PropTypes.bool,
dropShadow: PropTypes.bool,
+ /**
+ * Array of image objects for multiple images display
+ */
+ images: PropTypes.arrayOf(
+ PropTypes.shape({
+ src: PropTypes.string.isRequired,
+ alt: PropTypes.string.isRequired,
+ cssClasses: PropTypes.arrayOf(PropTypes.string),
+ loading: PropTypes.oneOf(["lazy", "eager"]),
+ decoding: PropTypes.oneOf(["sync", "async", "auto"]),
+ fetchPriority: PropTypes.oneOf(["auto", "high", "low"]),
+ width: PropTypes.string,
+ height: PropTypes.string,
+ dataTestId: PropTypes.string,
+ cardLink: PropTypes.string,
+ title: PropTypes.string,
+ caption: PropTypes.string,
+ captionTitle: PropTypes.string,
+ border: PropTypes.bool,
+ dropShadow: PropTypes.bool,
+ })
+ ),
+ /**
+ * Number of columns for multiple images display (0 for auto, 3 for three columns, 4 for four columns)
+ */
+ columns: PropTypes.oneOf(["0", "3", "4"]),
};
diff --git a/packages/unity-react-core/src/components/Image/Image.stories.jsx b/packages/unity-react-core/src/components/Image/Image.stories.jsx
index c163e3d573..f3a4b41007 100644
--- a/packages/unity-react-core/src/components/Image/Image.stories.jsx
+++ b/packages/unity-react-core/src/components/Image/Image.stories.jsx
@@ -3,10 +3,7 @@
import { imageAny } from "@asu/shared";
import React from "react";
-const img1 = imageAny(); // Placeholder for an example image
-const img2 = imageAny(); // Placeholder for an example image
-const img3 = imageAny(); // Placeholder for an example image
-const img4 = imageAny(); // Placeholder for an example image
+const img1 = imageAny();
import { Image } from "./Image";
@@ -41,7 +38,7 @@ ImageWithNoCaptionBorderless.args = {
export const ImageWithCaption = Template.bind({});
ImageWithCaption.args = {
- src: img2,
+ src: img1,
alt: "Placeholder image",
caption: "This is a caption.",
captionTitle: "Caption title",
@@ -50,7 +47,7 @@ ImageWithCaption.args = {
export const ImageWithCaptionAndDropshadow = Template.bind({});
ImageWithCaptionAndDropshadow.args = {
- src: img3,
+ src: img1,
alt: "Placeholder image",
caption: "This is a caption.",
captionTitle: "Caption title",
@@ -78,5 +75,83 @@ export const GridImages = GridTemplate.bind({});
GridImages.args = {
alt: "Placeholder image",
width: "100%",
- src: img4,
+ src: img1,
+};
+
+const MultipleImagesTemplate = args => ;
+
+export const TwoImagesAutoArrangement = MultipleImagesTemplate.bind({});
+TwoImagesAutoArrangement.args = {
+ images: [
+ {
+ src: img1,
+ alt: "First image",
+ border: true,
+ caption: "Caption for first image",
+ captionTitle: "First Image Title",
+ },
+ {
+ src: img1,
+ alt: "Second image",
+ border: true,
+ caption: "Caption for second image",
+ captionTitle: "Second Image Title",
+ dropShadow: true,
+ },
+ ],
+ columns: "0",
+};
+
+export const ThreeImagesArrangement = MultipleImagesTemplate.bind({});
+ThreeImagesArrangement.args = {
+ images: [
+ {
+ src: img1,
+ alt: "First image",
+ border: true,
+ caption: "Caption for first image",
+ },
+ {
+ src: img1,
+ alt: "Second image",
+ border: true,
+ caption: "Caption for second image",
+ dropShadow: true,
+ },
+ {
+ src: img1,
+ alt: "Third image",
+ border: true,
+ caption: "Caption for third image",
+ },
+ ],
+ columns: "3",
+};
+
+export const FourImagesArrangement = MultipleImagesTemplate.bind({});
+FourImagesArrangement.args = {
+ images: [
+ {
+ src: img1,
+ alt: "First image",
+ border: true,
+ },
+ {
+ src: img1,
+ alt: "Second image",
+ border: true,
+ dropShadow: true,
+ },
+ {
+ src: img1,
+ alt: "Third image",
+ border: true,
+ },
+ {
+ src: img1,
+ alt: "Fourth image",
+ border: true,
+ },
+ ],
+ columns: "4",
};
diff --git a/packages/unity-react-core/src/components/Image/Image.test.jsx b/packages/unity-react-core/src/components/Image/Image.test.jsx
index ed5a146724..94a724d9b4 100644
--- a/packages/unity-react-core/src/components/Image/Image.test.jsx
+++ b/packages/unity-react-core/src/components/Image/Image.test.jsx
@@ -6,6 +6,7 @@ import { expect, describe, it, afterEach, beforeEach } from "vitest";
// @ts-ignore
import { Image } from "./Image";
const img = imageAny();
+const img2 = imageAny();
const renderImage = props => {
return render();
@@ -26,4 +27,58 @@ describe("#Image", () => {
it("should define component", () => {
expect(component).toBeDefined();
});
+
+ describe("Multiple Images", () => {
+ it("should render multiple images with auto arrangement", () => {
+ const { container } = renderImage({
+ images: [
+ { src: img, alt: "First image" },
+ { src: img2, alt: "Second image" },
+ ],
+ columns: "0",
+ });
+
+ expect(container.querySelector(".uds-card-arrangement")).toBeTruthy();
+ expect(container.querySelector(".auto-arrangement")).toBeTruthy();
+ expect(container.querySelectorAll("img")).toHaveLength(2);
+ });
+
+ it("should render multiple images with three columns", () => {
+ const { container } = renderImage({
+ images: [
+ { src: img, alt: "First image" },
+ { src: img2, alt: "Second image" },
+ ],
+ columns: "3",
+ });
+
+ expect(container.querySelector(".uds-card-arrangement")).toBeTruthy();
+ expect(container.querySelector(".three-columns")).toBeTruthy();
+ expect(container.querySelectorAll("img")).toHaveLength(2);
+ });
+
+ it("should render multiple images with four columns", () => {
+ const { container } = renderImage({
+ images: [
+ { src: img, alt: "First image" },
+ { src: img2, alt: "Second image" },
+ ],
+ columns: "4",
+ });
+
+ expect(container.querySelector(".uds-card-arrangement")).toBeTruthy();
+ expect(container.querySelector(".four-columns")).toBeTruthy();
+ expect(container.querySelectorAll("img")).toHaveLength(2);
+ });
+
+ it("should render single image when images prop is not provided", () => {
+ const { container } = renderImage({
+ src: img,
+ alt: "Single image",
+ });
+
+ expect(container.querySelector(".uds-card-arrangement")).toBeFalsy();
+ expect(container.querySelectorAll("img")).toHaveLength(1);
+ });
+ });
});
diff --git a/packages/unity-react-core/src/components/RankingCard/RankingCard.jsx b/packages/unity-react-core/src/components/RankingCard/RankingCard.jsx
index 15e8d007a4..64e8ef23e6 100644
--- a/packages/unity-react-core/src/components/RankingCard/RankingCard.jsx
+++ b/packages/unity-react-core/src/components/RankingCard/RankingCard.jsx
@@ -7,6 +7,10 @@ import { GaEventWrapper } from "../GaEventWrapper/GaEventWrapper";
import { useBaseSpecificFramework } from "../GaEventWrapper/useBaseSpecificFramework";
import { Image } from "../Image/Image";
+/**
+ * @typedef {import('../../core/types/ranking-card-types').RankingCardProps} RankingCardProps
+ */
+
const gaDefaultObject = {
name: "onclick",
event: "link",
@@ -119,7 +123,7 @@ InfoLayerWrapper.propTypes = {
readMoreLink: PropTypes.string,
};
-export const RankingCard = ({
+const BaseRankingCard = ({
imageSize = "large",
image,
imageAlt,
@@ -161,27 +165,106 @@ export const RankingCard = ({
);
};
+BaseRankingCard.propTypes = {
+ imageSize: PropTypes.oneOf(["small", "large"]),
+ image: PropTypes.string.isRequired,
+ imageAlt: PropTypes.string.isRequired,
+ heading: PropTypes.string.isRequired,
+ body: PropTypes.string.isRequired,
+ readMoreLink: PropTypes.string,
+ citation: PropTypes.string,
+};
+
+/**
+ * @param {RankingCardProps} props
+ * @returns {JSX.Element}
+ */
+export const RankingCard = ({
+ imageSize = "large",
+ image,
+ imageAlt,
+ heading,
+ body,
+ readMoreLink = "",
+ citation,
+ cards = [],
+ columns = "0",
+}) => {
+ // If multiple cards are provided, render them in a card container
+ if (cards.length > 1) {
+ const getColumnClass = () => {
+ switch (columns) {
+ case "2":
+ return "";
+ case "3":
+ return "three-columns";
+ case "4":
+ return "four-columns";
+ default:
+ return "";
+ }
+ };
+
+ return (
+
+
+ {cards.map((card, index) => (
+
+ ))}
+
+
+ );
+ }
+
+ return (
+
+ );
+};
+
RankingCard.propTypes = {
/**
* Size of ranking card
*/
- imageSize: PropTypes.oneOf(["small", "large"]).isRequired,
+ imageSize: PropTypes.oneOf(["small", "large"]),
/**
* Ranking card image
*/
- image: PropTypes.string.isRequired,
+ image: PropTypes.string,
/**
* Card header image alt text
*/
- imageAlt: PropTypes.string.isRequired,
+ imageAlt: PropTypes.string,
/**
* Ranking card heading
*/
- heading: PropTypes.string.isRequired,
+ heading: PropTypes.string,
/**
* Ranking card body content
*/
- body: PropTypes.string.isRequired,
+ body: PropTypes.string,
/**
* Link for read more
*/
@@ -190,4 +273,22 @@ RankingCard.propTypes = {
* Ranking card citation content (Required for small size only)
*/
citation: PropTypes.string,
+ /**
+ * Array of ranking card objects for rendering multiple cards
+ */
+ cards: PropTypes.arrayOf(
+ PropTypes.shape({
+ imageSize: PropTypes.oneOf(["small", "large"]),
+ image: PropTypes.string.isRequired,
+ imageAlt: PropTypes.string.isRequired,
+ heading: PropTypes.string.isRequired,
+ body: PropTypes.string.isRequired,
+ readMoreLink: PropTypes.string,
+ citation: PropTypes.string,
+ })
+ ),
+ /**
+ * Number of columns for multiple cards layout (0, 2, 3, or 4)
+ */
+ columns: PropTypes.oneOf(["0", "2", "3", "4"]),
};
diff --git a/packages/unity-react-core/src/components/RankingCard/RankingCard.stories.jsx b/packages/unity-react-core/src/components/RankingCard/RankingCard.stories.jsx
index a3bf3445eb..e09571b6b3 100644
--- a/packages/unity-react-core/src/components/RankingCard/RankingCard.stories.jsx
+++ b/packages/unity-react-core/src/components/RankingCard/RankingCard.stories.jsx
@@ -28,7 +28,7 @@ Large.args = {
image: img,
imageAlt: "Image alt text",
heading: "Ranking title goes here, under the photo",
- body: "ASU has topped U.S. News & World Report’s “Most Innovative Schools list since the inception of the category in 2016. ASU again placed ahead of Stanford and MIT on the list, based on a survey of peers. College presidents, provosts and admissions deans around the country nominated up to 10 colleges or universities that are making the most innovative improvements.",
+ body: 'ASU has topped U.S. News & World Report\'s "Most Innovative Schools" list since the inception of the category in 2016. ASU again placed ahead of Stanford and MIT on the list, based on a survey of peers. College presidents, provosts and admissions deans around the country nominated up to 10 colleges or universities that are making the most innovative improvements.',
readMoreLink: "https://www.asu.edu/",
};
@@ -38,8 +38,221 @@ Small.args = {
image: img,
imageAlt: "Image alt text",
heading: "Ranking title goes here, under the photo",
- body: "ASU has topped U.S. News & World Report’s “Most Innovative Schools list since the inception of the category in 2016. ASU again placed ahead of Stanford and MIT on the list, based on a survey of peers. College presidents, provosts and admissions deans around the country nominated up to 10 colleges or universities that are making the most innovative improvements.",
+ body: 'ASU has topped U.S. News & World Report\'s "Most Innovative Schools" list since the inception of the category in 2016. ASU again placed ahead of Stanford and MIT on the list, based on a survey of peers. College presidents, provosts and admissions deans around the country nominated up to 10 colleges or universities that are making the most innovative improvements.',
readMoreLink: "https://www.asu.edu/",
citation:
"Citation of the ranking should go under the headline, regular body style text",
};
+
+const MultipleRankingCardsTemplate = args => (
+
+);
+
+export const MultipleRankingCardsTwoColumns = MultipleRankingCardsTemplate.bind(
+ {}
+);
+MultipleRankingCardsTwoColumns.args = {
+ columns: "2",
+ cards: [
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "First ranking image",
+ heading: "Most Innovative University",
+ body: "ASU has topped U.S. News & World Report's Most Innovative Schools list since the inception of the category in 2016.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "Second ranking image",
+ heading: "Top Online Programs",
+ body: "ASU Online is ranked #1 for innovation and #2 for best online bachelor's programs by U.S. News & World Report.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "small",
+ image: img,
+ imageAlt: "Third ranking image",
+ heading: "Research Excellence",
+ body: "ASU is classified as a Research 1 university by the Carnegie Classification of Institutions of Higher Education.",
+ readMoreLink: "https://www.asu.edu/",
+ citation: "Carnegie Classification 2021",
+ },
+ {
+ imageSize: "small",
+ image: img,
+ imageAlt: "Fourth ranking image",
+ heading: "Sustainability Leader",
+ body: "ASU is recognized as a leader in sustainability and climate action by multiple organizations.",
+ readMoreLink: "https://www.asu.edu/",
+ citation: "Times Higher Education Impact Rankings 2023",
+ },
+ ],
+};
+
+export const MultipleRankingCardsFourColumns =
+ MultipleRankingCardsTemplate.bind({});
+MultipleRankingCardsFourColumns.args = {
+ columns: "4",
+ cards: [
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "First ranking image",
+ heading: "Most Innovative",
+ body: "ASU has topped U.S. News & World Report's Most Innovative Schools list since 2016.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "Second ranking image",
+ heading: "Online Excellence",
+ body: "ASU Online is ranked #1 for innovation in online education programs.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "Third ranking image",
+ heading: "Research Impact",
+ body: "ASU is a Carnegie R1 research university with significant research impact.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "Fourth ranking image",
+ heading: "Sustainability",
+ body: "ASU leads in sustainability and climate action initiatives globally.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "First ranking image",
+ heading: "Most Innovative",
+ body: "ASU has topped U.S. News & World Report's Most Innovative Schools list since 2016.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "Second ranking image",
+ heading: "Online Excellence",
+ body: "ASU Online is ranked #1 for innovation in online education programs.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "Third ranking image",
+ heading: "Research Impact",
+ body: "ASU is a Carnegie R1 research university with significant research impact.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "Fourth ranking image",
+ heading: "Sustainability",
+ body: "ASU leads in sustainability and climate action initiatives globally.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "First ranking image",
+ heading: "Most Innovative",
+ body: "ASU has topped U.S. News & World Report's Most Innovative Schools list since 2016.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "Second ranking image",
+ heading: "Online Excellence",
+ body: "ASU Online is ranked #1 for innovation in online education programs.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "Third ranking image",
+ heading: "Research Impact",
+ body: "ASU is a Carnegie R1 research university with significant research impact.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "Fourth ranking image",
+ heading: "Sustainability",
+ body: "ASU leads in sustainability and climate action initiatives globally.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ ],
+};
+
+export const MultipleRankingCardsThreeColumns =
+ MultipleRankingCardsTemplate.bind({});
+MultipleRankingCardsThreeColumns.args = {
+ columns: "3",
+ cards: [
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "First ranking image",
+ heading: "Most Innovative",
+ body: "ASU has topped U.S. News & World Report's Most Innovative Schools list since 2016.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "Second ranking image",
+ heading: "Online Excellence",
+ body: "ASU Online is ranked #1 for innovation in online education programs.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "Third ranking image",
+ heading: "Research Impact",
+ body: "ASU is a Carnegie R1 research university with significant research impact.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "Fourth ranking image",
+ heading: "Sustainability",
+ body: "ASU leads in sustainability and climate action initiatives globally.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "Fifth ranking image",
+ heading: "Student Success",
+ body: "ASU excels in student outcomes and graduation rates across all programs.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "Sixth ranking image",
+ heading: "Global Reach",
+ body: "ASU's global impact and international partnerships span all continents.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ ],
+};
diff --git a/packages/unity-react-core/src/components/RankingCard/RankingCard.test.jsx b/packages/unity-react-core/src/components/RankingCard/RankingCard.test.jsx
index 25dcd3917e..46d71cf52c 100644
--- a/packages/unity-react-core/src/components/RankingCard/RankingCard.test.jsx
+++ b/packages/unity-react-core/src/components/RankingCard/RankingCard.test.jsx
@@ -73,3 +73,64 @@ describe("RankingCard small layout", () => {
expect(infoLayer).toHaveClass("show");
});
});
+
+describe("Multiple RankingCards", () => {
+ const multipleRankingCardsArgs = {
+ columns: "2",
+ cards: [
+ {
+ imageSize: "large",
+ image: img,
+ imageAlt: "First ranking image",
+ heading: "Most Innovative University",
+ body: "ASU has topped U.S. News & World Report's Most Innovative Schools list since 2016.",
+ readMoreLink: "https://www.asu.edu/",
+ },
+ {
+ imageSize: "small",
+ image: img,
+ imageAlt: "Second ranking image",
+ heading: "Top Online Programs",
+ body: "ASU Online is ranked #1 for innovation.",
+ readMoreLink: "https://www.asu.edu/",
+ citation: "U.S. News & World Report 2023",
+ },
+ ],
+ };
+
+ let component;
+
+ beforeEach(() => {
+ component = renderRankingCard(multipleRankingCardsArgs);
+ });
+
+ afterEach(cleanup);
+
+ it("should render multiple ranking cards container", () => {
+ const parentContainer = component.container.querySelector(
+ ".uds-card-arrangement"
+ );
+ const cardContainer = component.container.querySelector(
+ ".uds-card-arrangement-card-container"
+ );
+ expect(parentContainer).toBeInTheDocument();
+ expect(cardContainer).toBeInTheDocument();
+ });
+
+ it("should render correct number of ranking cards", () => {
+ const cards = component.container.querySelectorAll(".card-ranking");
+ expect(cards).toHaveLength(2);
+ });
+
+ it("should apply auto-arrangement class", () => {
+ const container = component.container.querySelector(".auto-arrangement");
+ expect(container).toBeInTheDocument();
+ });
+
+ it("should apply four-columns class when columns is 4", () => {
+ const fourColumnArgs = { ...multipleRankingCardsArgs, columns: "4" };
+ component = renderRankingCard(fourColumnArgs);
+ const container = component.container.querySelector(".four-columns");
+ expect(container).toBeInTheDocument();
+ });
+});
diff --git a/packages/unity-react-core/src/core/types/card-types.js b/packages/unity-react-core/src/core/types/card-types.js
index 346b4caa22..81449dcc38 100644
--- a/packages/unity-react-core/src/core/types/card-types.js
+++ b/packages/unity-react-core/src/core/types/card-types.js
@@ -11,6 +11,30 @@
* @property {boolean} [horizontal]
* @property {string} [image]
* @property {string} [imageAltText]
+ * @property {string} [title]
+ * @property {string[]} [icon]
+ * @property {string} [body]
+ * @property {string} [eventLocation]
+ * @property {string} [eventTime]
+ * @property {string} [linkLabel]
+ * @property {string} [linkUrl]
+ * @property {ButtonProps[]} [buttons]
+ * @property {"stack"|"inline"} [eventFormat]
+ * @property {"25%"|"50%"|"75%"|"100%"} [width]
+ * @property {TagsProps[]} [tags]
+ * @property {boolean} [showBorders]
+ * @property {string} [cardLink]
+ * @property {CardItemProps[]} [cards]
+ * @property {"0"|"2"|"3"|"4"} [columns]
+ * @property {"vertical" | "auto" } [layout]
+ */
+
+/**
+ * @typedef {Object} CardItemProps
+ * @property {string} [type]
+ * @property {boolean} [horizontal]
+ * @property {string} [image]
+ * @property {string} [imageAltText]
* @property {string} title
* @property {string[]} [icon]
* @property {string} [body]
diff --git a/packages/unity-react-core/src/core/types/image-types.js b/packages/unity-react-core/src/core/types/image-types.js
index d46ecf0f5f..63f17269bb 100644
--- a/packages/unity-react-core/src/core/types/image-types.js
+++ b/packages/unity-react-core/src/core/types/image-types.js
@@ -1,7 +1,7 @@
// @ts-check
/**
- * @typedef {Object} ImageComponentProps
+ * @typedef {Object} ImageItemProps
* @property {string} src
* @property {string} alt
* @property {Array.} [cssClasses]
@@ -19,6 +19,27 @@
* @property {boolean} [dropShadow]
*/
+/**
+ * @typedef {Object} ImageComponentProps
+ * @property {string} [src]
+ * @property {string} [alt]
+ * @property {Array.} [cssClasses]
+ * @property {"lazy"|"eager"} [loading]
+ * @property {"sync"|"async"|"auto"} [decoding]
+ * @property {"auto"|"high"|"low"} [fetchPriority]
+ * @property {string} [width]
+ * @property {string} [height]
+ * @property {string} [dataTestId]
+ * @property {string} [cardLink]
+ * @property {string} [title]
+ * @property {string} [caption]
+ * @property {string} [captionTitle]
+ * @property {boolean} [border]
+ * @property {boolean} [dropShadow]
+ * @property {Array.} [images] - Array of image objects for multiple images display
+ * @property {"0"|"3"|"4"} [columns] - Number of columns for multiple images display (0 for auto, 3 for three columns, 4 for four columns)
+ */
+
/**
* This help VSCODE and JSOC to recognize the syntax
* `import(FILE_PATH).EXPORTED_THING`
diff --git a/packages/unity-react-core/src/core/types/ranking-card-types.js b/packages/unity-react-core/src/core/types/ranking-card-types.js
new file mode 100644
index 0000000000..14037c5974
--- /dev/null
+++ b/packages/unity-react-core/src/core/types/ranking-card-types.js
@@ -0,0 +1,32 @@
+// @ts-check
+
+/**
+ * @typedef {Object} RankingCardProps
+ * @property {"small"|"large"} [imageSize]
+ * @property {string} [image]
+ * @property {string} [imageAlt]
+ * @property {string} [heading]
+ * @property {string} [body]
+ * @property {string} [readMoreLink]
+ * @property {string} [citation]
+ * @property {RankingCardItemProps[]} [cards]
+ * @property {"0"|"2"|"3"|"4"} [columns]
+ */
+
+/**
+ * @typedef {Object} RankingCardItemProps
+ * @property {"small"|"large"} [imageSize]
+ * @property {string} image
+ * @property {string} imageAlt
+ * @property {string} heading
+ * @property {string} body
+ * @property {string} [readMoreLink]
+ * @property {string} [citation]
+ */
+
+/**
+ * This help VSCODE and JSOC to recognize the syntax
+ * `import(FILE_PATH).EXPORTED_THING`
+ * @ignore
+ */
+export const JSDOC = "jsdoc";