Skip to content

Commit

Permalink
add: missing breadcrumbs to all pages
Browse files Browse the repository at this point in the history
  • Loading branch information
joonatank committed Jan 16, 2025
1 parent 47022fd commit e4b8a17
Show file tree
Hide file tree
Showing 21 changed files with 261 additions and 249 deletions.
17 changes: 14 additions & 3 deletions apps/ui/components/application/ApplicationPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
import { useRouter } from "next/router";
import Stepper, { StepperProps } from "./Stepper";
import NotesWhenApplying from "@/components/application/NotesWhenApplying";
import { getApplicationPath } from "@/modules/urls";
import BreadcrumbWrapper from "../common/BreadcrumbWrapper";
import { applicationsPrefix, getApplicationPath } from "@/modules/urls";
import { Breadcrumb } from "../common/Breadcrumb";
import { H1 } from "common";

const InnerContainer = styled.div<{ $hideStepper: boolean }>`
Expand Down Expand Up @@ -111,9 +111,20 @@ export function ApplicationPageWrapper({
const title = t(`${translationKeyPrefix}.heading`);
const subTitle =
headContent || overrideText || t(`${translationKeyPrefix}.text`);

const routes = [
{
slug: applicationsPrefix,
title: t("breadcrumb:applications"),
},
{
title: t("breadcrumb:application"),
},
] as const;

return (
<>
<BreadcrumbWrapper route={["/applications", "application"]} />
<Breadcrumb routes={routes} />
<div>
<H1 $noMargin>{title}</H1>
<p>{subTitle}</p>
Expand Down
160 changes: 66 additions & 94 deletions apps/ui/components/common/Breadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
import React, { ElementType, Fragment } from "react";
import React from "react";
import styled, { css } from "styled-components";
import { IconAngleLeft, IconAngleRight } from "hds-react";
import { fontMedium } from "common";
import { breakpoints, fontMedium } from "common";
import { useMedia } from "react-use";
import Link from "next/link";
import { Flex } from "common/styles/util";
import { truncatedText } from "common/styles/cssFragments";
import { useTranslation } from "next-i18next";

export type RouteItem = {
title: string;
slug?: string;
};

type Props = {
routes: RouteItem[];
isMobile: boolean;
linkComponent?: ElementType;
className?: string;
};

const limits = {
default: 25,
current: 40,
routes: Readonly<RouteItem[]>;
};

const Nav = styled.nav<{ $isMobile?: boolean }>`
font-size: var(--fontsize-body-m);
display: flex;
align-items: center;
line-height: var(--spacing-3-xl);
Expand All @@ -35,19 +31,24 @@ const Nav = styled.nav<{ $isMobile?: boolean }>`
}
`;

const Item = styled.div`
display: flex;
align-items: center;
max-width: 100%;
`;
const Item = styled(Flex).attrs({
$alignItems: "center",
$direction: "row",
$gap: "none",
})``;

const currentCss = css`
color: var(--color-black);
${fontMedium}
`;

const Anchor = styled.span<{ $current?: boolean; $isMobile?: boolean }>`
// const LIMIT_CURRENT_CH = 40;
const LIMIT_DEFAULT_CH = 25;
const Anchor = styled(Link)<{ $current?: boolean; $isMobile?: boolean }>`
&& {
${truncatedText}
display: inline-block;
max-width: ${LIMIT_DEFAULT_CH}ch;
${({ $current }) => {
switch ($current) {
case true:
Expand All @@ -61,102 +62,73 @@ const Anchor = styled.span<{ $current?: boolean; $isMobile?: boolean }>`
}
}}
}
${({ $isMobile }) =>
$isMobile &&
`
overflow: hidden;
text-overflow: ellipsis;
}
`};
white-space: nowrap;
`;

const Slug = styled.span<{ $current?: boolean }>`
${({ $current }) => $current && currentCss}
white-space: nowrap;
${truncatedText}
max-width: ${LIMIT_DEFAULT_CH}ch;
`;

export function Breadcrumb({
routes = [],
function BreadcrumbImpl({
routes,
isMobile,
linkComponent,
className,
}: Props): JSX.Element {
const Link = linkComponent || Fragment;

}: Pick<Props, "routes"> & { isMobile: boolean }): JSX.Element | null {
const routesWithSlug = routes?.filter((n) => n.slug != null && n.slug !== "");
const lastRoute = routes[routes.length - 1];
const lastRouteWithSlug = routesWithSlug[routesWithSlug.length - 1];

// TODO why are we doing this? is there a case where we need to do this?
// or would it just be better to pass hideMobileBreadcrumb prop to the component
// instead of having hidden logic here?
const isMobileEnabled =
isMobile &&
routesWithSlug.length > 1 &&
routesWithSlug.length > 0 &&
lastRoute.slug !== lastRouteWithSlug.slug;

if (!isMobileEnabled && isMobile) {
return null;
}

if (isMobile) {
return (
<Item>
<IconAngleLeft size="s" aria-hidden="true" />
<Anchor href={lastRouteWithSlug?.slug ?? ""} $isMobile>
{lastRouteWithSlug.title}
</Anchor>
</Item>
);
}

return (
<Nav
className={className}
data-testid="breadcrumb__wrapper"
$isMobile={isMobile}
>
{!isMobile ? (
routes?.map((item, index) => (
<Item key={`${item.title}${item.slug}`}>
{index > 0 && (
<IconAngleRight size="s" aria-hidden className="angleRight" />
)}
{item.slug ? (
<Link {...(linkComponent && { href: item.slug, passHref: true })}>
<Anchor
{...(!linkComponent && { href: item.slug })}
$current={index === routes.length - 1}
>
{index === routes.length - 1
? item.title.length > limits.current
? `${item.title.slice(0, limits.current)}...`
: item.title
: item.title.length > limits.default
? `${item.title.slice(0, limits.default)}...`
: item.title}
</Anchor>
</Link>
) : (
<Slug $current={index === routes.length - 1}>
{index === routes.length - 1
? item.title.length > limits.current
? `${item.title.slice(0, limits.current)}...`
: item.title
: item.title.length > limits.default
? `${item.title.slice(0, limits.default)}...`
: item.title}
</Slug>
)}
</Item>
))
) : isMobileEnabled ? (
<Item>
<IconAngleLeft size="s" aria-hidden className="angleLeft" />
<Link
{...(linkComponent && {
href: lastRouteWithSlug?.slug,
passHref: true,
})}
>
<Anchor
{...(!linkComponent && { href: lastRouteWithSlug?.slug })}
$isMobile
>
{lastRouteWithSlug.title}
<>
{routes.map((item, index) => (
<Item key={`${item.title}${item.slug}`}>
{index > 0 && <IconAngleRight size="s" aria-hidden="true" />}
{item.slug ? (
<Anchor href={item.slug} $current={index === routes.length - 1}>
{item.title}
</Anchor>
</Link>
) : (
<Slug $current={index === routes.length - 1}>{item.title}</Slug>
)}
</Item>
) : null}
))}
</>
);
}

export function Breadcrumb({ routes }: Props): JSX.Element {
const { t } = useTranslation();
const isMobile = useMedia(`(max-width: ${breakpoints.m})`, false);

const routesWithFrontPage = [
{ title: t("breadcrumb:frontpage"), slug: "/" },
...routes,
];
return (
<Nav data-testid="breadcrumb__wrapper" $isMobile={isMobile}>
<BreadcrumbImpl routes={routesWithFrontPage} isMobile={isMobile} />
</Nav>
);
}
43 changes: 0 additions & 43 deletions apps/ui/components/common/BreadcrumbWrapper.tsx

This file was deleted.

8 changes: 7 additions & 1 deletion apps/ui/modules/applicationRound.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { type ApplicationRoundNode } from "common/gql/gql-types";
import { getTranslation } from "./util";
import { Maybe } from "@/gql/gql-types";

export function getApplicationRoundName(
applicationRound: Pick<ApplicationRoundNode, "nameFi" | "nameSv" | "nameEn">
applicationRound?:
| Maybe<Pick<ApplicationRoundNode, "nameFi" | "nameSv" | "nameEn">>
| undefined
): string {
if (applicationRound == null) {
return "-";
}
return getTranslation(applicationRound, "name");
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
formatApiTimeInterval,
} from "common/src/helpers";
import { getApplicationPath } from "@/modules/urls";
import BreadcrumbWrapper from "@/components/common/BreadcrumbWrapper";
import { useTranslation } from "next-i18next";
import { gql } from "@apollo/client";
import { breakpoints, H1 } from "common";
Expand All @@ -42,6 +41,7 @@ import { Card } from "common/src/components";
import styled from "styled-components";
import { isReservationCancellable } from "@/modules/reservation";
import { ConfirmationDialog } from "common/src/components/ConfirmationDialog";
import { Breadcrumb } from "@/components/common/Breadcrumb";

type PropsNarrowed = Exclude<Props, { notFound: boolean }>;

Expand Down Expand Up @@ -131,7 +131,7 @@ function ReservationCancelPage(props: PropsNarrowed): JSX.Element {

return (
<>
<BreadcrumbWrapper route={routes} />
<Breadcrumb routes={routes} />
<ReservationPageWrapper>
<div>
<H1 $noMargin>{title}</H1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import { H1 } from "common";
import { AllReservations } from "@/components/application/ApprovedReservations";
import { gql } from "@apollo/client";
import { useTranslation } from "next-i18next";
import BreadcrumbWrapper from "@/components/common/BreadcrumbWrapper";
import { Breadcrumb } from "@/components/common/Breadcrumb";
import {
convertLanguageCode,
getTranslationSafe,
} from "common/src/common/util";
import { getApplicationPath } from "@/modules/urls";
import { applicationsPrefix, getApplicationPath } from "@/modules/urls";

function ViewAll({ applicationSection }: PropsNarrowed): JSX.Element {
const { t, i18n } = useTranslation();
Expand All @@ -29,21 +29,21 @@ function ViewAll({ applicationSection }: PropsNarrowed): JSX.Element {
const lang = convertLanguageCode(i18n.language);
const route = [
{
slug: "/applications",
slug: applicationsPrefix,
title: t("breadcrumb:applications"),
},
{
slug: getApplicationPath(application.pk, "view"),
title: getTranslationSafe(applicationRound, "name", lang),
},
{
slug: "",
title: applicationSection.name,
},
];
] as const;

return (
<>
<BreadcrumbWrapper route={route} />
<Breadcrumb routes={route} />
<H1 $noMargin>{heading}</H1>
<AllReservations
applicationSection={applicationSection}
Expand Down
Loading

0 comments on commit e4b8a17

Please sign in to comment.