Skip to content

Commit

Permalink
HCBS Header (#14)
Browse files Browse the repository at this point in the history
Co-authored-by: Rocio De Santiago <rociodesantiago@CoformaSantiago.lan>
  • Loading branch information
rocio-desantiago and Rocio De Santiago authored Jul 31, 2024
1 parent 5af6073 commit b550eda
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 7 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed services/ui-src/src/assets/logos/logo_mdct_mfp.png
Binary file not shown.
16 changes: 12 additions & 4 deletions services/ui-src/src/components/app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { useStore } from "utils";
import { useContext } from "react";
import { Routes, Route } from "react-router-dom";
import { LoginCognito, LoginIDM, PostLogoutRedirect, Footer } from "components";
import { makeMediaQueryClasses } from "utils/other/useBreakpoint";
import { Container, Divider, Flex, Heading, Stack } from "@chakra-ui/react";
import {
Header,
LoginCognito,
LoginIDM,
PostLogoutRedirect,
Footer,
} from "components";
import { makeMediaQueryClasses, UserContext, useStore } from "utils";

export const App = () => {
const mqClasses = makeMediaQueryClasses();
// const context = useContext(UserContext);
const context = useContext(UserContext);
const { logout } = context;
const { user, showLocalLogins } = useStore();
// const { pathname } = useLocation();

Expand All @@ -26,6 +33,7 @@ export const App = () => {
<>
{user && (
<Flex data-testid="app-container" sx={sx.appLayout}>
<Header handleLogout={logout} />
<Footer />
</Flex>
)}
Expand Down
10 changes: 7 additions & 3 deletions services/ui-src/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ export { ErrorAlert } from "./alerts/ErrorAlert";
// app
export { App } from "./app/App";
export { Error } from "./app/Error";
// logins
export { LoginCognito } from "./logins/LoginCognito";
export { LoginIDM } from "./logins/LoginIDM";
// layout
export { Header } from "./layout/Header";
export { PageTemplate } from "./layout/PageTemplate";
export { Footer } from "./layout/Footer";
// logins
export { LoginCognito } from "./logins/LoginCognito";
export { LoginIDM } from "./logins/LoginIDM";
// menus
export { Menu } from "./menus/Menu";
export { MenuOption } from "./menus/MenuOption";
// Redirects
export { PostLogoutRedirect } from "./PostLogoutRedirect/index";
59 changes: 59 additions & 0 deletions services/ui-src/src/components/layout/Header.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { RouterWrappedComponent } from "utils/testing/setupJest";
import { Header, MenuOption } from "components";
import { testA11y } from "utils/testing/commonTests";

const headerComponent = (
<RouterWrappedComponent>
<Header handleLogout={() => {}} />
</RouterWrappedComponent>
);

describe("<Header />", () => {
describe("Test Visibility of Header", () => {
beforeEach(() => {
render(headerComponent);
});

test("Navigation, Logo, Help and Menu is visible on Header", () => {
const header = screen.getByRole("navigation");
expect(header).toBeVisible();
expect(screen.getByAltText("HCBS logo")).toBeVisible();
expect(screen.getByAltText("Help")).toBeVisible();
expect(screen.getByAltText("Arrow down")).toBeVisible();
});

test("Renders My Account menu and is clickable", () => {
render(
<MenuOption
text={"My Account"}
icon={"icon_arrrow_down.png"}
altText={"Arrow down"}
/>
);

const menuButton = screen.getByRole("button", { name: /my account/i });
expect(menuButton).toBeInTheDocument();

userEvent.click(menuButton);
});

test("Logs out user", () => {
render(
<MenuOption
text={"Log Out"}
icon={"icon_arrow_right_square.png"}
altText={"Logout"}
/>
);

const logoutButton = screen.getByRole("img", { name: /Logout/i });
expect(logoutButton).toBeInTheDocument();

userEvent.click(logoutButton);
});
});

testA11y(headerComponent);
});
91 changes: 91 additions & 0 deletions services/ui-src/src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Link as RouterLink } from "react-router-dom";
import { UsaBanner } from "@cmsgov/design-system";
import { Box, Container, Flex, Image, Link } from "@chakra-ui/react";
import { Menu, MenuOption } from "components";
import { useBreakpoint } from "utils";
import appLogo from "assets/logos/logo_mdct_hcbs.png";
import getHelpIcon from "assets/icons/icon_help.png";

export const Header = ({ handleLogout }: Props) => {
const { isMobile } = useBreakpoint();

return (
<Box sx={sx.root} id="header">
<Flex sx={sx.usaBannerContainer}>
<UsaBanner />
</Flex>
<Flex sx={sx.headerBar} role="navigation">
<Container sx={sx.headerContainer}>
<Flex sx={sx.headerFlex}>
<Link as={RouterLink} to="/" variant="unstyled">
<Image src={appLogo} alt="HCBS logo" sx={sx.appLogo} />
</Link>
<Flex sx={sx.menuFlex}>
<Link
as={RouterLink}
to="/help"
variant="unstyled"
aria-label="Get Help"
>
<MenuOption
icon={getHelpIcon}
text="Get Help"
altText="Help"
role="group"
hideText={isMobile}
/>
</Link>
<Menu handleLogout={handleLogout} />
</Flex>
</Flex>
</Container>
</Flex>
</Box>
);
};

interface Props {
handleLogout: () => void;
}

const sx = {
root: {
position: "sticky",
top: 0,
zIndex: "sticky",
boxShadow: "0px 4px 4px rgba(0, 0, 0, 0.25)",
"@media print": {
display: "none",
},
},
usaBannerContainer: {
width: "100%",
flexDirection: "column",
alignItems: "center",
backgroundColor: "palette.gray_lightest",
".desktop &": {
padding: "0 1rem",
},
},
headerBar: {
minHeight: "4rem",
alignItems: "center",
bg: "palette.primary_darkest",
},
headerContainer: {
maxW: "appMax",
".desktop &": {
padding: "0 2rem",
},
},
headerFlex: {
justifyContent: "space-between",
alignItems: "center",
},
menuFlex: {
alignItems: "center",
},
appLogo: {
maxWidth: "200px",
},
};
20 changes: 20 additions & 0 deletions services/ui-src/src/components/menus/Menu.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { render, screen } from "@testing-library/react";
import { RouterWrappedComponent } from "utils/testing/setupJest";
import { Menu } from "components";
import { testA11y } from "utils/testing/commonTests";

const menuComponent = (
<RouterWrappedComponent>
<Menu handleLogout={() => {}} />
</RouterWrappedComponent>
);

describe("<Menu />", () => {
test("Menu button is visible", () => {
render(menuComponent);

expect(screen.getByRole("button", { name: "my account" })).toBeVisible();
});

testA11y(menuComponent);
});
91 changes: 91 additions & 0 deletions services/ui-src/src/components/menus/Menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import {
Box,
Button,
Image,
Menu as MenuRoot,
MenuButton,
MenuItem,
MenuList,
} from "@chakra-ui/react";
import { MenuOption } from "components";
import { useBreakpoint } from "utils";
import accountCircleIcon from "assets/icons/icon_account_circle.png";
import chevronDownIcon from "assets/icons/icon_arrow_down.png";
import logoutIcon from "assets/icons/icon_arrow_right_square.png";

export const Menu = ({ handleLogout }: Props) => {
const { isMobile } = useBreakpoint();
return (
<MenuRoot offset={[8, 20]}>
<Box role="group">
<MenuButton
as={Button}
rightIcon={
<Image src={chevronDownIcon} alt="Arrow down" sx={sx.menuIcon} />
}
sx={sx.menuButton}
aria-label="my account"
>
<MenuOption
icon={accountCircleIcon}
altText="Account"
text="My Account"
hideText={isMobile}
/>
</MenuButton>
</Box>
<MenuList sx={sx.menuList} data-testid="header-menu-options-list">
<MenuItem
onClick={handleLogout}
sx={sx.menuItem}
tabIndex={0}
data-testid="header-menu-option-log-out"
>
<MenuOption icon={logoutIcon} text="Log Out" altText="Logout" />
</MenuItem>
</MenuList>
</MenuRoot>
);
};

interface Props {
handleLogout: () => void;
}

const sx = {
menuButton: {
padding: 0,
paddingRight: ".5rem",
marginLeft: ".5rem",
borderRadius: 0,
background: "none",
color: "palette.white",
fontWeight: "bold",
_hover: { color: "palette.secondary_light", background: "none !important" },
_active: { background: "none" },
_focus: {
boxShadow: "none",
outline: "0px solid transparent !important",
},
".mobile &": {
marginLeft: 0,
},
"& .chakra-button__icon": {
marginInlineStart: "0rem",
},
},
menuList: {
padding: "0",
border: "none",
background: "palette.primary_darkest",
boxShadow: "0px 5px 16px rgba(0, 0, 0, 0.14)",
},
menuItem: {
borderRadius: ".375rem",
_focus: { background: "palette.primary_darker" },
_hover: { background: "palette.primary_darker" },
},
menuIcon: {
width: "0.75rem",
},
};
34 changes: 34 additions & 0 deletions services/ui-src/src/components/menus/MenuOption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Flex, Image, Text } from "@chakra-ui/react";

export const MenuOption = ({ text, icon, altText, role, hideText }: Props) => {
return (
<Flex
align="center"
role={role}
sx={!hideText ? { paddingRight: ".5rem" } : {}}
>
<Image src={icon} alt={altText} sx={sx.menuIcon} />
{!hideText && <Text sx={sx.text}>{text}</Text>}
</Flex>
);
};

interface Props {
text: string;
icon: string;
altText: string;
role?: string;
hideText?: boolean;
}

const sx = {
text: {
fontWeight: "bold",
color: "palette.white",
_groupHover: { color: "palette.gray_lighter" },
},
menuIcon: {
width: "1.5rem",
margin: "0.5rem",
},
};

0 comments on commit b550eda

Please sign in to comment.