Skip to content

Commit 1f385f7

Browse files
authored
Addition of footer (#12)
* Adding footer and footer stories * Added in props option to allow for custom props. Fixed styling. * tests added for footer * coverage folder added to git ignore * Standardised exports * Coverage job added to json package * resolving MR comments. * removing duped from p json * jest-transform-stub added for tests to pass with svgs * addressing MR comments. * adding extra checks for tests * removed all data-testid from tests and component * Footer now appears at the bottom of the page and not sticky * Footer links will stack wehen page gets narrower * addressing MR comments * addressed MR comments to copyright text * added company instead of text * addressing MR commet to allow changing of styles without TS complaining
1 parent d16ac65 commit 1f385f7

File tree

9 files changed

+299
-26
lines changed

9 files changed

+299
-26
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/dist/
22
/node_modules/
3+
/coverage
34

45
*storybook.log
56

jest.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
module.exports = {
22
testEnvironment: "jsdom",
3+
moduleNameMapper: {
4+
'^.+.(svg)$': 'jest-transform-stub',
5+
}
36
};

package.json

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"lint": "eslint .",
1414
"rollup": "rollup --config rollup.config.mjs",
1515
"jest": "jest --config jest.config.js",
16+
"jest:coverage": "jest --coverage",
1617
"storybook": "storybook dev -p 6006",
1718
"storybook:build": "storybook build -o storybook-static",
1819
"storybook:publish": "gh-pages -b storybook/publish -d storybook-static"
@@ -22,21 +23,17 @@
2223
"types": "dist/index.d.ts",
2324
"dependencies": {
2425
"@mui/icons-material": "^6.1.7",
26+
"jest-transform-stub": "^2.0.0",
2527
"react-icons": "^5.3.0"
2628
},
2729
"peerDependencies": {
28-
"react": "^18.3.1",
29-
"react-dom": "^18.3.1",
30-
"@mui/material": "^6.1.7",
3130
"@emotion/react": "^11.13.3",
32-
"@emotion/styled": "^11.13.0"
31+
"@emotion/styled": "^11.13.0",
32+
"@mui/material": "^6.1.7",
33+
"react": "^18.3.1",
34+
"react-dom": "^18.3.1"
3335
},
3436
"devDependencies": {
35-
"react": "^18.3.1",
36-
"react-dom": "^18.3.1",
37-
"@mui/material": "^6.1.7",
38-
"@emotion/react": "^11.13.3",
39-
"@emotion/styled": "^11.13.0",
4037
"@babel/core": "^7.26.0",
4138
"@babel/preset-env": "^7.26.0",
4239
"@babel/preset-react": "^7.25.9",
@@ -78,8 +75,7 @@
7875
"storybook-dark-mode": "^4.0.2",
7976
"tslib": "^2.8.1",
8077
"typescript": "^5.6.3",
81-
"typescript-eslint": "^8.15.0",
82-
"@testing-library/jest-dom": "^6.6.3"
78+
"typescript-eslint": "^8.15.0"
8379
},
8480
"packageManager": "[email protected]+sha256.24235772cc4ac82a62627cd47f834c72667a2ce87799a846ec4e8e555e2d4b8b"
8581
}

src/components/Footer.stories.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Meta, StoryObj } from "@storybook/react/*";
2+
import { Footer, FooterLink, FooterLinks } from "./Footer";
3+
4+
const meta: Meta<typeof Footer> = {
5+
title: "SciReactUI/Navigation/Footer",
6+
component: Footer,
7+
decorators: [(Story) => <Story />],
8+
tags: ["autodocs"],
9+
};
10+
11+
export default meta;
12+
type Story = StoryObj<typeof meta>;
13+
14+
export const LogoOnly: Story = {
15+
args: {},
16+
};
17+
18+
export const CopyrightOnly: Story = {
19+
args: {
20+
logo: "",
21+
copyright: "Company",
22+
},
23+
};
24+
25+
export const CopyrightAndLogo: Story = {
26+
args: { copyright: "Company" },
27+
};
28+
29+
export const WithOneLink: Story = {
30+
args: {
31+
copyright: "Company",
32+
children: [
33+
<FooterLinks key="footer-links">
34+
<FooterLink href="#" key="first-footer-link">
35+
Link one
36+
</FooterLink>
37+
</FooterLinks>,
38+
],
39+
},
40+
};
41+
42+
export const WithTwoLinks: Story = {
43+
args: {
44+
copyright: "Company",
45+
children: [
46+
<FooterLinks key="footer-links">
47+
<FooterLink href="#" key="first-footer-link">
48+
Link one
49+
</FooterLink>
50+
<FooterLink href="#" key="second-footer-link">
51+
Link two
52+
</FooterLink>
53+
</FooterLinks>,
54+
],
55+
},
56+
};

src/components/Footer.test.tsx

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { render, screen, waitFor } from "@testing-library/react";
2+
import "@testing-library/jest-dom";
3+
4+
import dlsLogo from "../public/dls.svg";
5+
6+
import { Footer, FooterLink, FooterLinks } from "./Footer";
7+
describe("Footer", () => {
8+
test("Should render logo only", async () => {
9+
render(<Footer logo={dlsLogo} />);
10+
11+
await waitFor(() => {
12+
expect(screen.getByRole("img")).toBeInTheDocument();
13+
// No copyright text
14+
expect(screen.queryByRole("paragraph")).not.toBeTruthy();
15+
});
16+
});
17+
18+
test("Should render copyright only", async () => {
19+
const copyrightText = "add text here";
20+
render(<Footer logo={null} copyright={copyrightText} />);
21+
22+
await waitFor(() => {
23+
expect(screen.queryByRole("paragraph")).toBeInTheDocument();
24+
expect(screen.queryByRole("paragraph")?.textContent).toStrictEqual(
25+
`Copyright © 2024 ${copyrightText}`
26+
);
27+
// No logo
28+
expect(screen.queryByRole("img")).not.toBeTruthy();
29+
});
30+
});
31+
32+
test("Should render logo and copyright", async () => {
33+
const copyrightText = "add text here";
34+
render(<Footer logo={dlsLogo} copyright={copyrightText} />);
35+
36+
await waitFor(() => {
37+
expect(screen.getByRole("img")).toBeInTheDocument();
38+
expect(screen.queryByRole("paragraph")).toBeInTheDocument();
39+
expect(screen.queryByRole("paragraph")?.textContent).toStrictEqual(
40+
`Copyright © 2024 ${copyrightText}`
41+
);
42+
});
43+
});
44+
45+
test("Should render with one link", async () => {
46+
const lineOneText = "Link one";
47+
const linkOneName = "link-one-href";
48+
49+
render(
50+
<Footer>
51+
<FooterLinks>
52+
<FooterLink href={linkOneName}>{lineOneText}</FooterLink>
53+
</FooterLinks>
54+
</Footer>
55+
);
56+
57+
await waitFor(() => {
58+
const linkOneContainer = screen.getByText(lineOneText);
59+
60+
expect(linkOneContainer).toBeInTheDocument();
61+
expect(linkOneContainer.getAttribute("href")).toStrictEqual(linkOneName);
62+
expect(linkOneContainer.textContent).toStrictEqual(lineOneText);
63+
});
64+
});
65+
66+
test("Should render with two links", async () => {
67+
const linkOneText = "Link one";
68+
const linkTwoText = "Link two";
69+
const linkOneName = "link-one-href";
70+
const linkTwoName = "link-two-href";
71+
render(
72+
<Footer>
73+
<FooterLinks>
74+
<FooterLink href={linkOneName}>{linkOneText}</FooterLink>
75+
<FooterLink href={linkTwoName}>{linkTwoText}</FooterLink>
76+
</FooterLinks>
77+
</Footer>
78+
);
79+
80+
await waitFor(() => {
81+
const linkTwoContainer = screen.getByText(linkTwoText);
82+
83+
expect(linkTwoContainer).toBeInTheDocument();
84+
expect(linkTwoContainer.getAttribute("href")).toStrictEqual(linkTwoName);
85+
expect(linkTwoContainer.textContent).toStrictEqual(linkTwoText);
86+
});
87+
});
88+
});

src/components/Footer.tsx

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { Link, LinkProps, Typography, useTheme } from "@mui/material";
2+
import Grid from "@mui/material/Grid2";
3+
import dlsLogo from "../public/logo-short.svg";
4+
import React from "react";
5+
6+
interface FooterLinksProps {
7+
children: React.ReactElement<LinkProps> | React.ReactElement<LinkProps>[];
8+
}
9+
10+
interface FooterProps extends React.HTMLProps<HTMLDivElement> {
11+
/** Location/content of the logo */
12+
logo?: string | null;
13+
copyright?: string | null;
14+
children?: React.ReactElement | React.ReactElement[];
15+
}
16+
17+
const FooterLinks = ({ children }: FooterLinksProps) => {
18+
return (
19+
<div
20+
style={{
21+
float: "left",
22+
alignItems: "center",
23+
borderTop: "4px solid transparent",
24+
borderBottom: "4px solid transparent",
25+
display: "flex",
26+
flexWrap: "wrap",
27+
}}
28+
>
29+
{children}
30+
</div>
31+
);
32+
};
33+
34+
const FooterLink = ({ children, ...props }: LinkProps) => {
35+
const theme = useTheme();
36+
37+
return (
38+
<Link
39+
sx={{
40+
"&:hover": {
41+
color: theme.palette.secondary.main,
42+
borderBottom: "solid 4px",
43+
},
44+
textDecoration: "none",
45+
color: theme.palette.primary.contrastText,
46+
marginLeft: "1.5rem",
47+
cursor: "pointer",
48+
}}
49+
{...props}
50+
>
51+
{children}
52+
</Link>
53+
);
54+
};
55+
56+
/*
57+
* Basic footer bar.
58+
* Can be used with `FooterLinks` and `FooterLink` to display a list of links.
59+
*/
60+
const Footer = ({
61+
logo = dlsLogo as string,
62+
copyright,
63+
children,
64+
...props
65+
}: FooterProps) => {
66+
const theme = useTheme();
67+
68+
return (
69+
<footer
70+
style={{
71+
bottom: 0,
72+
marginTop: "auto",
73+
minHeight: 50,
74+
backgroundColor: theme.palette.primary.light,
75+
}}
76+
{...props}
77+
>
78+
<Grid container>
79+
<Grid
80+
size={{ xs: 6, md: 8 }}
81+
style={{
82+
alignContent: "center",
83+
}}
84+
>
85+
{children}
86+
</Grid>
87+
<Grid size={{ xs: 6, md: 4 }}>
88+
<div
89+
style={{
90+
float: "right",
91+
paddingTop: "10px",
92+
paddingRight: "15px",
93+
textAlign: "right",
94+
}}
95+
>
96+
{logo ? <img alt="footer-logo" src={logo} /> : null}
97+
{copyright ? (
98+
<Typography
99+
style={{
100+
margin: 0,
101+
color: theme.palette.primary.contrastText,
102+
}}
103+
>
104+
{`Copyright © ${new Date().getFullYear()} ${copyright}`}
105+
</Typography>
106+
) : null}
107+
</div>
108+
</Grid>
109+
</Grid>
110+
</footer>
111+
);
112+
};
113+
114+
export { Footer, FooterLinks, FooterLink };
115+
export type { FooterLinksProps, FooterProps };

src/components/Navbar.tsx

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
useTheme,
1212
} from "@mui/material";
1313
import { MdMenu, MdClose } from "react-icons/md";
14-
import logoImage from "../public/logo-dark.svg"
14+
import logoImage from "../public/logo-dark.svg";
1515
import React, { useState } from "react";
1616

1717
interface NavLinksProps {
@@ -83,12 +83,12 @@ const NavLinks = ({ children }: NavLinksProps) => {
8383
{children}
8484
</Stack>
8585
<Drawer
86-
open={isOpen}
87-
onClose={onClose}
88-
anchor="left"
89-
PaperProps={{
90-
sx:{backgroundColor:theme.palette.primary.main}
91-
}}
86+
open={isOpen}
87+
onClose={onClose}
88+
anchor="left"
89+
PaperProps={{
90+
sx: { backgroundColor: theme.palette.primary.main },
91+
}}
9292
>
9393
<Box
9494
sx={{
@@ -116,13 +116,13 @@ const Navbar = ({
116116
...props
117117
}: NavbarProps) => {
118118
const theme = useTheme();
119-
119+
120120
return (
121-
<Box position="sticky" top="0" zIndex={1} width="100%" {...props}>
121+
<Box top="0" zIndex={1} width="100%" {...props}>
122122
<Paper
123123
sx={{
124124
display: "flex",
125-
backgroundColor:theme.palette.primary.main,
125+
backgroundColor: theme.palette.primary.main,
126126
px: { xs: "1rem", md: "7.5vw" },
127127
height: 50,
128128
width: "100%",
@@ -138,11 +138,13 @@ const Navbar = ({
138138
>
139139
{logo ? (
140140
<Link href="/" key="logo">
141-
<Box maxWidth="5rem"
141+
<Box
142+
maxWidth="5rem"
142143
sx={{
143-
"&:hover": { filter: "brightness(80%);" }
144-
}}>
145-
<img
144+
"&:hover": { filter: "brightness(80%);" },
145+
}}
146+
>
147+
<img
146148
alt="Home"
147149
src={logo}
148150
width={"100px"}
@@ -159,4 +161,4 @@ const Navbar = ({
159161
};
160162

161163
export { Navbar, NavLinks, NavLink };
162-
export type {NavLinksProps, NavbarProps };
164+
export type { NavLinksProps, NavbarProps };

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// components
22
export * from "./components/Breadcrumbs";
33
export * from "./components/Navbar";
4+
export * from "./components/Footer";
45
export * from "./components/User";
56
export * from "./components/VisitInput";
67

0 commit comments

Comments
 (0)