Skip to content

Commit

Permalink
feat: Implement Link Hub Component (#376)
Browse files Browse the repository at this point in the history
  • Loading branch information
johglove committed Dec 19, 2023
1 parent b739863 commit 059e9a0
Show file tree
Hide file tree
Showing 9 changed files with 316 additions and 29 deletions.
44 changes: 44 additions & 0 deletions src/components/PageContent/LinkHub/LinkHub.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
Copyright (C) 2018 The Trustees of Indiana University
SPDX-License-Identifier: BSD-3-Clause
*/
import classNames from "classnames";
import * as PropTypes from "prop-types";
import * as React from "react";
import * as Rivet from "../../util/Rivet";
import { TestUtils } from "../../util/TestUtils";
import LinkHubItem from "./LinkHubItem"

const LinkHub = ({
children,
className,
testMode = false,
variant = "normal",
...attrs
}) => {
const classNameArr = [
"rvt-link-hub",
variant === "stacked" ? "rvt-link-hub--stacked" : "",
className
]
return (
<ul
className={classNames(classNameArr)}
{...(testMode && { "data-testid": TestUtils.LinkHub.container })}
{...attrs}
>
{children}
</ul>
)
};

LinkHub.displayName = "LinkHub";
LinkHub.propTypes = {
testMode: PropTypes.bool,
/** The variant determines the style of the link hub */
variant: PropTypes.oneOf(["normal", "stacked"]),
};

LinkHub.Item = LinkHubItem;

export default Rivet.rivetize(LinkHub);
31 changes: 31 additions & 0 deletions src/components/PageContent/LinkHub/LinkHub.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Use the link hub component to show a list of links with optional descriptions.

Link hubs are often used on website home pages, section index pages, and landing pages to guide the user toward resources related to a single topic.

View the [Rivet documentation for Link-hub](https://rivet.uits.iu.edu/components/link-hub/).

### Standard Link Hub Example

<!-- prettier-ignore-start -->
```jsx
<LinkHub>
<LinkHub.Item label="Link 1" url="#" />
<LinkHub.Item label="Link 2" url="#">Link with an optional description</LinkHub.Item>
<LinkHub.Item label="Link 3" url="#" />
<LinkHub.Item label="Link 4" url="#">Another link with an optional description</LinkHub.Item>
</LinkHub>
```
<!-- prettier-ignore-end -->

### Stacked Link Hub Example

<!-- prettier-ignore-start -->
```jsx
<LinkHub variant="stacked">
<LinkHub.Item label="Link 1" url="#" />
<LinkHub.Item label="Link 2" url="#">Link with an optional description</LinkHub.Item>
<LinkHub.Item label="Link 3" url="#" />
<LinkHub.Item label="Link 4" url="#">Another link with an optional description</LinkHub.Item>
</LinkHub>
```
<!-- prettier-ignore-end -->
151 changes: 151 additions & 0 deletions src/components/PageContent/LinkHub/LinkHub.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import React from "react";
import LinkHub from "./LinkHub";
import { TestUtils } from "../../util/TestUtils";

const testIds = TestUtils.LinkHub

const customClassName = "custom-style"
const customClassNameItem = "custom-style-item"

describe("<LinkHub />", () => {
describe("Rendering", () => {
it("should render without throwing an error", () => {
render(
<LinkHub
className={customClassName}
testMode
>
<LinkHub.Item label="Link 1" url="#" />
<LinkHub.Item label="Link 2" url="#" />
<LinkHub.Item label="Link 3" url="#" />
</LinkHub>
);
checkRenderLinkHubContainer()
});
});
describe("Options", () => {
it("Stack variant renders correctly", () => {
render(
<LinkHub
className={customClassName}
testMode
variant="stacked"
>
<LinkHub.Item label="Link 1" url="#" />
<LinkHub.Item label="Link 2" url="#" />
<LinkHub.Item label="Link 3" url="#" />
</LinkHub>
);
checkRenderLinkHubContainer()
const element = screen.getByTestId(testIds.container);
expect(element).toHaveClass("rvt-link-hub--stacked");
});
it("default is test mode off", () => {
render(
<LinkHub className={customClassName}>
<LinkHub.Item label="Link 1" url="#" />
<LinkHub.Item label="Link 2" url="#" />
<LinkHub.Item label="Link 3" url="#" />
</LinkHub>
);
const element = screen.queryByTestId(testIds.container);
expect(element).not.toBeInTheDocument();
});
});
});

describe("<LinkHub.Item />", () => {
describe("Rendering", () => {
const label = "Link 1"
const url = "www.testLink1.com"
const description = "description text"
it("should render without throwing an error", () => {
render(
<LinkHub.Item
className={customClassNameItem}
label={label}
testMode
url={url}
>
{description}
</LinkHub.Item>
);
checkRenderLinkHubItemContainer()
checkRenderLinkHubItemLink(label, url, description)
});
it("without description, should render without throwing an error", () => {
render(
<LinkHub.Item
className={customClassNameItem}
label={label}
testMode
url={url}
/>
);
checkRenderLinkHubItemContainer()
checkRenderLinkHubItemLink(label, url, null)
});
});
describe("Options", () => {
it("default is test mode off", () => {
render(
<LinkHub.Item
label="Link 1"
url="www.testLink1.com"
>
description text
</LinkHub.Item>
);
const element = screen.queryByTestId(testIds.itemContainer);
expect(element).not.toBeInTheDocument();
});
});
});



const checkRenderLinkHubContainer = () => {
const element = screen.getByTestId(testIds.container);
expect(element.nodeName).toBe("UL")
expect(element).toBeVisible();
expect(element).toHaveClass("rvt-link-hub");
expect(element).toHaveClass(customClassName);
expect(element.children.length).toBe(3)
}

const checkRenderLinkHubItemContainer = () => {
const element = screen.getByTestId(testIds.itemContainer);
expect(element.nodeName).toBe("LI")
expect(element).toBeVisible();
expect(element).toHaveClass("rvt-link-hub__item");
expect(element).toHaveClass(customClassNameItem);
expect(element.children.length).toBe(1)
}

const checkRenderLinkHubItemLink= (label, url, description = null) => {
const element = screen.getByTestId(testIds.itemLink);
expect(element.nodeName).toBe("A")
expect(element).toBeVisible();
expect(element).toHaveClass("rvt-link-hub__link");
expect(element).toHaveAttribute("href", url);

const hasDescription = description != null
expect(element.children.length).toBe(hasDescription ? 2 : 1)

const labelElement = element.children[0]
expect(labelElement.nodeName).toBe("SPAN")
expect(labelElement).toBeVisible();
expect(labelElement).toHaveClass("rvt-link-hub__text");
expect(labelElement.innerHTML).toBe(label);

if(hasDescription) {
const descriptionElement = element.children[1]
expect(descriptionElement.nodeName).toBe("SPAN")
expect(descriptionElement).toBeVisible();
expect(descriptionElement).toHaveClass("rvt-link-hub__description");
expect(descriptionElement.innerHTML).toBe(description);
}

}
51 changes: 51 additions & 0 deletions src/components/PageContent/LinkHub/LinkHubItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
Copyright (C) 2018 The Trustees of Indiana University
SPDX-License-Identifier: BSD-3-Clause
*/
import classNames from "classnames";
import * as PropTypes from "prop-types";
import * as React from "react";
import * as Rivet from "../../util/Rivet";
import { TestUtils } from "../../util/TestUtils";

const LinkHubItem = ({
className,
children,
label,
testMode = false,
url,
...attrs
}) => {
const classNameArr = [
"rvt-link-hub__item",
className
]
return (
<li
className={classNames(classNameArr)}
{...(testMode && { "data-testid": TestUtils.LinkHub.itemContainer })}
{...attrs}
>
<a
className="rvt-link-hub__link"
href={url}
{...(testMode && { "data-testid": TestUtils.LinkHub.itemLink })}
>
<span className="rvt-link-hub__text">{label}</span>
{children && <span className="rvt-link-hub__description">{children}</span>}
</a>
</li>
)
};

LinkHubItem.displayName = "LinkHubItem";
LinkHubItem.propTypes = {
/** The label for the item link */
label: PropTypes.string.isRequired,
testMode: PropTypes.bool,
/** The url for the item link */
url: PropTypes.string.isRequired
};


export default Rivet.rivetize(LinkHubItem);
1 change: 1 addition & 0 deletions src/components/PageContent/LinkHub/LinkHubItem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Use the `LinkHub.Item` to add items to `LinkHub`. See `LinkHub` for usage.
28 changes: 0 additions & 28 deletions src/components/PageContent/Stat/Stat.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,4 @@ const image = (
```
<!-- prettier-ignore-end -->

### Stat Group Examples

<!-- prettier-ignore-start -->
```jsx
<StatGroup>
<Stat href="#" value="100">Sample Stat 1</Stat>
<Stat href="#" value="50">Sample Stat 2</Stat>
<Stat href="#" value="25">Sample Stat 3</Stat>
<Stat href="#" value="10">Sample Stat 4</Stat>
</StatGroup>
```
<!-- prettier-ignore-end -->

<!-- prettier-ignore-start -->
```jsx
const image = (
<div class="rvt-avatar">
<img class="rvt-avatar__image" src="https://rivet.iu.edu/img/placeholder/avatar-1.webp" alt="" />
</div>
);

<StatGroup>
<Stat href="#" image={image} value="100">Sample Stat 1</Stat>
<Stat href="#" image={image} value="50">Sample Stat 2</Stat>
<Stat href="#" image={image} value="25">Sample Stat 3</Stat>
<Stat href="#" image={image} value="10">Sample Stat 4</Stat>
</StatGroup>
```
<!-- prettier-ignore-end -->
31 changes: 31 additions & 0 deletions src/components/PageContent/Stat/StatGroup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Use the `StatGroup` to group multiple `Stat` components together.

### Stat Group Examples

<!-- prettier-ignore-start -->
```jsx
<StatGroup>
<Stat href="#" value="100">Sample Stat 1</Stat>
<Stat href="#" value="50">Sample Stat 2</Stat>
<Stat href="#" value="25">Sample Stat 3</Stat>
<Stat href="#" value="10">Sample Stat 4</Stat>
</StatGroup>
```
<!-- prettier-ignore-end -->

<!-- prettier-ignore-start -->
```jsx
const image = (
<div class="rvt-avatar">
<img class="rvt-avatar__image" src="https://rivet.iu.edu/img/placeholder/avatar-1.webp" alt="" />
</div>
);

<StatGroup>
<Stat href="#" image={image} value="100">Sample Stat 1</Stat>
<Stat href="#" image={image} value="50">Sample Stat 2</Stat>
<Stat href="#" image={image} value="25">Sample Stat 3</Stat>
<Stat href="#" image={image} value="10">Sample Stat 4</Stat>
</StatGroup>
```
<!-- prettier-ignore-end -->
1 change: 1 addition & 0 deletions src/components/PageContent/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export { default as CallToAction } from "./CallToAction/CallToAction";
export { default as Disclosure } from "./Disclosure/Disclosure";
export { default as EmptyState } from "./EmptyState/EmptyState";
export { default as Hero } from "./Hero/Hero";
export { default as LinkHub } from "./LinkHub/LinkHub";
export { default as Quote } from "./Quote/Quote";
export { default as Stat } from "./Stat/Stat";
export { default as StatGroup } from "./Stat/StatGroup";
Expand Down
7 changes: 6 additions & 1 deletion src/components/util/TestUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,10 @@ export const TestUtils = {
image: "stat-image",
number: "stat-number"
},
Timeline: { testId: "timeline__testId" }
Timeline: { testId: "timeline__testId" },
LinkHub: {
container: "linkhub-container",
itemContainer: "linkhub-description",
itemLink: "linkhub-group"
},
};

0 comments on commit 059e9a0

Please sign in to comment.