Skip to content
Draft
11 changes: 9 additions & 2 deletions packages/unity-bootstrap-theme/src/scss/extends/_images.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

// Images with captions
.uds-figure {
margin: 0;
width: 100%;
img.img-original {
width: initial;
Expand Down Expand Up @@ -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;
}
}
}
115 changes: 114 additions & 1 deletion packages/unity-react-core/src/components/Card/Card.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,69 @@ export const Card = ({
tags,
showBorders = true,
cardLink,
cards = [],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Card feels like it is doing too much. Should we have a card-arrangement component that takes an array of 1 or more cards?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am ok with this temporarily if we have to keep it for WS2 purposes, but think it will cause issue down the line since it is a strange pattern

columns = "0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are moving towards responsive design, and we should not allow columns to be a property. Columns should be responsive and allow the card to adjust between the min and max widths provided by the brand team.

layout = "auto"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does auto mean horizontal?
Instead of layout default to auto can we change layout prop to vertical = true or forceVertical = true?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see we already have horizontal prop. these props are getting confusing

}) => {
// 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 (
<div className={classNames("uds-card-arrangement", {
"uds-card-arrangement-vertical": layout === "vertical",
"auto-arrangement": layout !== "vertical"
})
}>
<div
className={classNames(
"uds-card-arrangement-card-container",
getColumnClass()
)}
>
{cards.map((card, index) => (
<BaseCard
key={index}
type={card.type || type}
width={card.width || width}
horizontal={
card.horizontal !== undefined ? card.horizontal : horizontal
}
image={card.image}
imageAltText={card.imageAltText}
title={card.title}
icon={card.icon}
body={card.body}
eventFormat={card.eventFormat || eventFormat}
eventLocation={card.eventLocation}
eventTime={card.eventTime}
buttons={card.buttons}
linkLabel={card.linkLabel}
linkUrl={card.linkUrl}
tags={card.tags}
showBorders={
card.showBorders !== undefined ? card.showBorders : showBorders
}
cardLink={card.cardLink}
/>
))}
</div>
</div>
);
}

// Single card - render as before
return (
<BaseCard
type={type}
Expand Down Expand Up @@ -86,7 +148,7 @@ Card.propTypes = {
/**
* Card title
*/
title: PropTypes.string.isRequired,
title: PropTypes.string,
/**
React Font Awesome icon prefix and name string to be rendered in button label. Ex: ['fab', 'drupal']
*/
Expand Down Expand Up @@ -152,6 +214,57 @@ Card.propTypes = {
* Card link
*/
cardLink: PropTypes.string,
/**
* Array of card objects for rendering multiple cards
*/
cards: PropTypes.arrayOf(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cards has same structure as BaseCard, might need to rearrange the file, but can we reuse the prop structure

cards: PropTypes.arrayOf(PropTypes.shape(BaseCard.propTypes))

PropTypes.shape({
type: PropTypes.oneOf(["default", "degree", "event", "news", "story"]),
width: PropTypes.oneOf(["25%", "50%", "75%", "100%"]),
horizontal: PropTypes.bool,
title: PropTypes.string.isRequired,
icon: PropTypes.arrayOf(PropTypes.string),
body: PropTypes.string,
eventFormat: PropTypes.oneOf(["stack", "inline"]),
eventLocation: PropTypes.string,
eventTime: PropTypes.string,
image: PropTypes.string,
imageAltText: PropTypes.string,
buttons: PropTypes.arrayOf(
PropTypes.shape({
ariaLabel: PropTypes.string,
color: PropTypes.oneOf(["gold", "maroon", "gray", "dark"]),
icon: PropTypes.arrayOf(PropTypes.string),
href: PropTypes.string,
label: PropTypes.string,
onClick: PropTypes.func,
size: PropTypes.oneOf(["default", "small", "xsmall"]),
target: PropTypes.oneOf(["_blank", "_self", "_top", "_parent"]),
})
),
linkLabel: PropTypes.string,
linkUrl: PropTypes.string,
tags: PropTypes.arrayOf(
PropTypes.shape({
ariaLabel: PropTypes.string,
color: PropTypes.oneOf(["white", "gray", "dark"]),
href: PropTypes.string,
label: PropTypes.string,
onClick: PropTypes.func,
})
),
showBorders: PropTypes.bool,
cardLink: PropTypes.string,
})
),
/**
* Number of columns for multiple cards layout (0, 2, 3, or 4)
*/
columns: PropTypes.oneOf(["0", "2", "3", "4"]),
/**
* Vertical or normal layout
*/
layout: PropTypes.oneOf(["vertical", "auto"])
};

/*
Expand Down
131 changes: 130 additions & 1 deletion packages/unity-react-core/src/components/Card/Card.stories.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { imageAny } from "@asu/shared";
import classNames from "classnames";
import React from "react";
const img1 = imageAny(); // Placeholder for an example image
const img1 = imageAny();

// @ts-ignore
import { Card } from "./Card";
Expand Down Expand Up @@ -328,3 +328,132 @@ AsuNewsStory.parameters = {
},
},
};

const MultipleCardsTemplate = args => (
<div className="container">
<div className={classNames("row", "row-spaced", "pt-2", "pb-2")}>
<div className={classNames("col", "col-12")}>
<Card {...args} />
</div>
</div>
</div>
);

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",
},
],
};
56 changes: 55 additions & 1 deletion packages/unity-react-core/src/components/Card/Card.test.jsx
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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();
});
});
});
Loading