Skip to content

Commit 89e7bd5

Browse files
feat(unity-react-core): add new ranking card component
N
1 parent 703a717 commit 89e7bd5

6 files changed

Lines changed: 224 additions & 0 deletions

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// @ts-check
2+
import { sanitizeDangerousMarkup } from "@asu/shared";
3+
import classNames from "classnames";
4+
import PropTypes from "prop-types";
5+
import React from "react";
6+
7+
import { Button } from "../Button/Button";
8+
9+
/**
10+
* @typedef {Object} RankingItem
11+
* @property {string} value - The main ranking value (e.g., "400+", "#2")
12+
* @property {string} title - The title of the ranking (e.g., "‘prestigious faculty’")
13+
* @property {string} description - The description of the ranking
14+
*/
15+
16+
/**
17+
* @typedef {Object} HighlyRankedProps
18+
* @property {string} [title] - The section title
19+
* @property {string} [description] - The section description
20+
* @property {string} [ctaText] - The text for the CTA button
21+
* @property {string} [ctaUrl] - The URL for the CTA button
22+
* @property {"dark" | "gold" | "maroon" | "gray"} [ctaButtonColor] - The color of the CTA button
23+
* @property {RankingItem[]} [rankings] - The list of rankings to display
24+
*/
25+
26+
/**
27+
* @param {HighlyRankedProps} props
28+
* @returns {JSX.Element}
29+
*/
30+
export const HighlyRanked = ({
31+
title = "Highly ranked",
32+
description = "",
33+
ctaText = "",
34+
ctaUrl = "",
35+
ctaButtonColor = "dark",
36+
rankings = [],
37+
}) => {
38+
return (
39+
<section className="highly-ranked-section">
40+
<div className="container">
41+
<div className="row">
42+
<div className="col-12 col-lg-8">
43+
{title && (
44+
<h2 className="highly-ranked-title">
45+
<span className="highlight-gold">{title}</span>
46+
</h2>
47+
)}
48+
{description && (
49+
<p
50+
className="highly-ranked-description"
51+
dangerouslySetInnerHTML={sanitizeDangerousMarkup(description)}
52+
/>
53+
)}
54+
{ctaText && ctaUrl && (
55+
<div className="highly-ranked-cta">
56+
<Button
57+
label={ctaText}
58+
href={ctaUrl}
59+
color={ctaButtonColor}
60+
size={"small"}
61+
classes={[]}
62+
/>
63+
</div>
64+
)}
65+
</div>
66+
</div>
67+
68+
<div className="row highly-ranked-grid">
69+
{rankings.map((ranking, index) => (
70+
<div key={index} className="col-12 col-lg-6 mb-4">
71+
<div className="highly-ranked-card">
72+
<div className="ranking-value-container">
73+
<h3 className="ranking-value">{ranking.value}</h3>
74+
</div>
75+
<div className="ranking-content">
76+
{ranking.title && (
77+
<h4 className="ranking-title">{ranking.title}</h4>
78+
)}
79+
{ranking.description && (
80+
<p
81+
className="ranking-description"
82+
dangerouslySetInnerHTML={sanitizeDangerousMarkup(
83+
ranking.description
84+
)}
85+
/>
86+
)}
87+
</div>
88+
</div>
89+
</div>
90+
))}
91+
</div>
92+
</div>
93+
</section>
94+
);
95+
};
96+
97+
HighlyRanked.propTypes = {
98+
title: PropTypes.string,
99+
description: PropTypes.string,
100+
ctaText: PropTypes.string,
101+
ctaUrl: PropTypes.string,
102+
rankings: PropTypes.arrayOf(
103+
PropTypes.shape({
104+
value: PropTypes.string.isRequired,
105+
title: PropTypes.string,
106+
description: PropTypes.string,
107+
})
108+
),
109+
ctaButtonColor: PropTypes.oneOf(["dark", "gold", "maroon", "gray"]),
110+
};
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React from "react";
2+
import { HighlyRanked } from "./HighlyRanked";
3+
4+
export default {
5+
title: "Components/HighlyRanked",
6+
component: HighlyRanked,
7+
};
8+
9+
const Template = args => <HighlyRanked {...args} />;
10+
11+
export const Default = Template.bind({});
12+
Default.args = {
13+
title: "ASU is highly ranked",
14+
description:
15+
"ASU consistently ranks among the best in the nation, recognized for our commitment to excellence and showcasing our dedication to providing quality education and impactful research.",
16+
ctaText: "See more ASU rankings",
17+
ctaUrl: "https://www.asu.edu/rankings",
18+
rankings: [
19+
{
20+
value: "400+",
21+
title: "‘prestigious faculty’",
22+
description: "National Academies-honored faculty",
23+
},
24+
{
25+
value: "#2",
26+
title: "in the U.S. for employability",
27+
description: "among public universities",
28+
},
29+
{
30+
value: "83",
31+
title: "top-ranked programs",
32+
description: "Ranked in the top 25 in the U.S., including 38 in the top 10",
33+
},
34+
{
35+
value: "$6.1",
36+
title: "billion",
37+
description: "FY24 economic impact on the state’s gross product",
38+
},
39+
{
40+
value: "Top 10",
41+
title: "worldwide among universities granted U.S. patents",
42+
description: "For two years",
43+
},
44+
{
45+
value: "270+",
46+
title: "athletic championships",
47+
description: "National and conference titles",
48+
},
49+
{
50+
value: "620,000+",
51+
title: "alumni",
52+
description: "Leading, shaping, changing our world",
53+
},
54+
{
55+
value: "Association of American Universities (AAU) member",
56+
title: "",
57+
description: "Along with Harvard, Stanford and MIT",
58+
},
59+
{
60+
value: "Top producer of elite scholars",
61+
title: "",
62+
description: "For 10 consecutive years",
63+
},
64+
],
65+
};
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { render, screen } from "@testing-library/react";
2+
import React from "react";
3+
import { expect, describe, it } from "vitest";
4+
import { HighlyRanked } from "./HighlyRanked";
5+
6+
describe("HighlyRanked component", () => {
7+
const mockProps = {
8+
title: "ASU is highly ranked",
9+
description: "Test description",
10+
ctaText: "CTA Rank",
11+
ctaUrl: "#",
12+
rankings: [
13+
{
14+
value: "400+",
15+
title: "faculty",
16+
description: "description 1",
17+
},
18+
],
19+
};
20+
21+
it("should render the title and description", () => {
22+
render(<HighlyRanked {...mockProps} />);
23+
expect(screen.getByText("ASU is highly ranked")).toBeInTheDocument();
24+
expect(screen.getByText("Test description")).toBeInTheDocument();
25+
});
26+
27+
it("should render the CTA button", () => {
28+
render(<HighlyRanked {...mockProps} />);
29+
const cta = screen.getByText("CTA Rank");
30+
expect(cta).toBeInTheDocument();
31+
expect(cta.closest("a")).toHaveAttribute("href", "#");
32+
});
33+
34+
it("should render the ranking cards", () => {
35+
render(<HighlyRanked {...mockProps} />);
36+
expect(screen.getByText("400+")).toBeInTheDocument();
37+
expect(screen.getByText("faculty")).toBeInTheDocument();
38+
expect(screen.getByText("description 1")).toBeInTheDocument();
39+
});
40+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { initHighlyRanked as default } from "../../core/utils";

packages/unity-react-core/src/components/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export * from "./CardArrangement/CardArrangement";
1010
export * from "./Divider/Divider";
1111
export * from "./FeedAnatomy";
1212
export * from "./Hero/Hero";
13+
export * from "./HighlyRanked/HighlyRanked";
1314
export * from "./Image/Image";
1415
export * from "./Pagination/Pagination";
1516
export * from "./RankingCard/RankingCard";

packages/unity-react-core/src/core/utils/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import { Divider } from "../../components/Divider/Divider";
2121
import { GridLinks } from "../../components/GridLinks/GridLinks";
2222
import { Hero } from "../../components/Hero/Hero";
23+
import { HighlyRanked } from "../../components/HighlyRanked/HighlyRanked";
2324
import { Image } from "../../components/Image/Image";
2425
import { List } from "../../components/List/List";
2526
import { Pagination } from "../../components/Pagination/Pagination";
@@ -169,3 +170,9 @@ export const initTooltip = ({ targetSelector, props }) =>
169170
*/
170171
export const initList = ({ targetSelector, props }) =>
171172
RenderReact(List, props, document.querySelector(targetSelector));
173+
174+
/**
175+
* @param {ComponentProps} props
176+
*/
177+
export const initHighlyRanked = ({ targetSelector, props }) =>
178+
RenderReact(HighlyRanked, props, document.querySelector(targetSelector));

0 commit comments

Comments
 (0)