Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sidebar navigation): update sidebar navigation styling #1649

Merged
merged 8 commits into from
Jan 27, 2025
68 changes: 0 additions & 68 deletions app/components/__test__/FlowNavigation.test.tsx

This file was deleted.

6 changes: 5 additions & 1 deletion app/components/navigation/FlowNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { NavigationList } from "~/components/navigation/NavigationList";

export default function FlowNavigation(props: FlowNavigationProps) {
return (
<nav aria-label={props.a11yLabels?.menuLabel} className="bg-white">
<nav
role="navigation"
aria-label={props.a11yLabels?.menuLabel}
className="bg-white border-[1px] border-blue-400"
>
<NavigationList {...props} />
</nav>
);
Expand Down
39 changes: 18 additions & 21 deletions app/components/navigation/NavItem.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import CheckIcon from "@digitalservicebund/icons/Check";
import CheckCircle from "@digitalservicebund/icons/CheckCircle";
import ExpandLessIcon from "@digitalservicebund/icons/ExpandLess";
import ExpandMoreIcon from "@digitalservicebund/icons/ExpandMore";
import classNames from "classnames";
Expand All @@ -25,9 +25,9 @@ export const StateIcon: FC<{
id: string;
a11yLabels?: NavigationA11yLabels;
}> = ({ id, a11yLabels }) => (
<CheckIcon
<CheckCircle
id={id}
className="shrink-0"
className="shrink-0 fill-green-700"
aria-label={a11yLabels?.itemFinished}
/>
);
Expand All @@ -49,21 +49,21 @@ export function NavItem({
const isDone = stateIsDone(state);
const collapse = useCollapse({ defaultExpanded: isCurrent });

// Transparent left borders to avoid layout shifts
const liClassNames = classNames("list-none border-l-[1px] min-w-full", {
"text-gray-600 curser-not-allowed hover:font-normal pointer-events-none":
isDisabled,
"mb-1": !isChild, // margin instead of bottom border to avoid diagonal corners colors
"border-l-blue-800": isCurrent,
"border-l-blue-100": !isCurrent,
});
// Transparent last: borders to avoid layout shifts
const liClassNames = classNames(
"list-none border-b-[1px] border-blue-400 last:border-0 min-w-full",
{
"text-gray-600 curser-not-allowed hover:font-normal pointer-events-none":
isDisabled,
"border-transparent last:border-transparent": isChild,
},
);

const itemClassNames = classNames(
"bg-blue-100 w-full ds-label-02-reg p-16 border-l-[3px] border-transparent flex gap-x-4 items-center hover:underline hover:bg-blue-300 active:bg-white focus-visible:shadow-[inset_0px_0px_0px_4px] focus:shadow-blue-800",
"w-full ds-label-02-reg p-16 flex justify-between items-center hover:underline hover:bg-blue-400 active:bg-blue-300 focus-visible:shadow-[inset_0px_0px_0px_4px] focus:shadow-blue-300",
{
"ds-label-02-bold bg-blue-500 border-l-blue-800":
isCurrent && !hasSubflows,
"pl-40": isChild,
"ds-label-02-bold bg-blue-400": isCurrent && !hasSubflows,
"pl-24": isChild,
},
);
const iconId = useId();
Expand All @@ -79,18 +79,15 @@ export function NavItem({
{...collapse.getToggleProps()}
aria-describedby={iconId}
>
{isDone && <StateIcon id={iconId} a11yLabels={a11yLabels} />}
{label}
{collapse.isExpanded ? (
<ExpandLessIcon className="ml-auto" />
) : (
<ExpandMoreIcon className="ml-auto" />
)}
{isDone && <StateIcon id={iconId} a11yLabels={a11yLabels} />}
</button>
<section
className="border-t-[1px] border-white"
{...collapse.getCollapseProps()}
>
<section {...collapse.getCollapseProps()}>
<NavigationList
navItems={visibleChildItems}
a11yLabels={a11yLabels}
Expand All @@ -106,8 +103,8 @@ export function NavItem({
aria-current={isCurrent}
aria-describedby={iconId}
>
{isDone && <StateIcon id={iconId} a11yLabels={a11yLabels} />}
{label}
{isDone && <StateIcon id={iconId} a11yLabels={a11yLabels} />}
</a>
)}
</li>
Expand Down
20 changes: 20 additions & 0 deletions app/components/navigation/__test__/FlowNavigation.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { render, screen } from "@testing-library/react";
import FlowNavigation from "~/components/navigation/FlowNavigation";
import { NavState } from "~/services/navigation/navState";

describe("FlowNavigation", () => {
it("renders a navigation element", () => {
const navItems = [
{
destination: "/destination",
label: "navLabel",
state: NavState.Current,
},
];

render(<FlowNavigation navItems={navItems} />);

const nav = screen.getByRole("navigation");
expect(nav).toHaveClass("bg-white border-[1px] border-blue-400");
});
});
136 changes: 136 additions & 0 deletions app/components/navigation/__test__/NavItem.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { render, screen } from "@testing-library/react";
import { NavState } from "~/services/navigation/navState";
import { NavItem } from "../NavItem";

describe("NavigationItem", () => {
const destination = "/destination";
const label = "navLabel";

it("renders one navigation item", () => {
render(
<NavItem
destination={destination}
label={label}
state={NavState.Current}
/>,
);

expect(screen.getByRole("link")).toHaveTextContent(label);
expect(screen.getByRole("link")).toHaveAttribute("href", destination);
expect(screen.getByRole("listitem")).toBeInstanceOf(HTMLLIElement);
});
it("renders navigation item with a checkmark icon when state is done", () => {
render(
<NavItem destination={destination} label={label} state={NavState.Done} />,
);

const checkCircle = screen.getByTestId("CheckCircleIcon");
expect(checkCircle).toHaveClass("shrink-0 fill-green-700");
});

it("renders navigation item with the correct classNames when state is disabled", () => {
render(
<NavItem
destination={destination}
label={label}
state={NavState.Disabled}
/>,
);

screen
.getAllByRole<HTMLAnchorElement>("link")
.forEach((link) => expect(link).toHaveAttribute("aria-disabled", "true"));
expect(screen.getByRole("listitem")).toHaveClass(
"text-gray-600 curser-not-allowed hover:font-normal pointer-events-none",
);
});

it("renders navigation item with correct classNames when state is current", () => {
render(
<NavItem
destination={destination}
label={label}
state={NavState.Current}
/>,
);

expect(screen.getByRole("listitem")).toHaveClass(
"border-b-[1px] border-blue-400 last:border-0 min-w-full",
);
});

it("renders navigation item with correct classNames when isChild is true", () => {
render(
<NavItem
destination={destination}
label={label}
state={NavState.Current}
isChild={true}
/>,
);

expect(screen.getByRole("listitem")).toHaveClass(
"border-transparent last:border-transparent",
);
});

it("renders navigation item with subflows", () => {
const subflows = [
{
destination: "/subflow1",
label: "subflowLabel1",
state: NavState.Open,
},
{
destination: "/subflow2",
label: "subflowLabel2",
state: NavState.Disabled,
},
];
render(
<NavItem
destination={destination}
label={label}
state={NavState.Current}
subflows={subflows}
/>,
);

const subflow1 = screen.getByText("subflowLabel1");
expect(subflow1).toHaveAttribute("href", "/subflow1");
const subflow2 = screen.queryByText("subflowLabel2");
expect(subflow2).not.toBeInTheDocument();
});

it("renders subItem with correct classNames when isChild is true", () => {
const subflows = [
{
destination: "/subflow",
label: "subflowLabel",
state: NavState.Open,
},
];
render(
<NavItem
destination={destination}
label={label}
state={NavState.Current}
subflows={subflows}
/>,
);
expect(screen.getByText("subflowLabel")).toHaveClass("pl-24");
});

it("renders items with correct classNames when the state is current and item doesn't have subflows", () => {
render(
<NavItem
destination={destination}
label={label}
state={NavState.Current}
/>,
);
expect(screen.getByRole("link")).toHaveClass(
"ds-label-02-bold bg-blue-400",
);
});
});
22 changes: 22 additions & 0 deletions app/components/navigation/__test__/NavigationList.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { render, screen } from "@testing-library/react";
import { NavState } from "~/services/navigation/navState";
import { NavigationList } from "../NavigationList";

describe("NavigationList", () => {
it("renders a navigation list with one navigation item", () => {
const destination = "/destination";
const label = "navLabel";
const state = NavState.Current;

render(<NavigationList navItems={[{ destination, label, state }]} />);

const list = screen.getByRole("list");
expect(list).toBeInstanceOf(HTMLUListElement);
expect(list).toHaveClass("pl-0");

const item = screen.getAllByRole<HTMLAnchorElement>("link");
expect(item[0]).toHaveTextContent(label);
expect(item[0].href).toContain(destination);
expect(item[0].parentNode).toBeInstanceOf(HTMLLIElement);
});
});
Loading