Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/app/layout/Breadcrumb.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,11 @@ span.breadcrumbItemLink {
color: var(--colors-grey800);
padding: 0 4px;
}

.learnMoreIconLink {
display: inline-flex;
align-items: center;
margin-left: 4px;
vertical-align: middle;
color: var(--colors-grey700);
}
28 changes: 28 additions & 0 deletions src/app/layout/Breadcrumb.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,34 @@ describe('BreadcrumbItem', () => {
'#/overview/dataElements'
)
})

it('should render a learn-more link next to the label when it is the current page and a learnMoreUrl is given', () => {
const { getByRole, getByText } = render(
<BreadcrumbItem
label={'Data elements'}
to={'/'}
learnMoreUrl={'https://docs.dhis2.org/data-elements'}
/>,
{ wrapper: HashRouter }
)

expect(getByText('Data elements')).toBeDefined()
const learnMoreLink = getByRole('link')
expect(learnMoreLink).toHaveAttribute(
'href',
'https://docs.dhis2.org/data-elements'
)
expect(learnMoreLink).toHaveAttribute('target', '_blank')
})

it('should not render a learn-more link when no learnMoreUrl is given', () => {
const { queryByRole } = render(
<BreadcrumbItem label={'Data elements'} to={'/'} />,
{ wrapper: HashRouter }
)

expect(queryByRole('link')).not.toBeInTheDocument()
})
})
describe('Breadcrumbs', () => {
it('should render crumb components in handle', () => {
Expand Down
37 changes: 33 additions & 4 deletions src/app/layout/Breadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import i18n from '@dhis2/d2-i18n'
import { IconQuestion16 } from '@dhis2/ui-icons'
import React from 'react'
import { Link, To, useLocation, useMatches, matchPath } from 'react-router-dom'
import { useToWithSearchState } from '../../lib'
Expand All @@ -9,16 +11,23 @@ const BreadcrumbSeparator = () => <span className={css.separator}>/</span>
type BreadcrumbItemProps = {
label: string
to: To
learnMoreUrl?: string
}

export const BreadcrumbItem = ({ label, to }: BreadcrumbItemProps) => {
export const BreadcrumbItem = ({
label,
to,
learnMoreUrl,
}: BreadcrumbItemProps) => {
const resolvedTo = useToWithSearchState(to)
const currentLoc = useLocation()

if (resolvedTo.pathname) {
const match = matchPath(resolvedTo.pathname, currentLoc.pathname)
if (match?.pattern.end) {
return <BreadCrumbEndItem label={label} />
return (
<BreadCrumbEndItem label={label} learnMoreUrl={learnMoreUrl} />
)
}
}

Expand All @@ -35,8 +44,28 @@ export const BreadcrumbItem = ({ label, to }: BreadcrumbItemProps) => {

/** Component that is used for "End links", where the current route is the end of the path
* and thus should not be a link */
export const BreadCrumbEndItem = ({ label }: { label: string }) => (
<span className={css.breadcrumbItem}>{label}</span>
export const BreadCrumbEndItem = ({
label,
learnMoreUrl,
}: {
label: string
learnMoreUrl?: string
}) => (
<span className={css.breadcrumbItem}>
{label}
{learnMoreUrl && (
<a
className={css.learnMoreIconLink}
href={learnMoreUrl}
target="_blank"
rel="noopener noreferrer"
>
<span aria-label={i18n.t('Learn more')}>
<IconQuestion16 />
</span>
</a>
)}
</span>
)

export const Breadcrumbs = () => {
Expand Down
5 changes: 4 additions & 1 deletion src/app/routes/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
useBoundResourceQueryFn,
useSectionHandle,
} from '../../lib'
import { OverviewSection } from '../../types'
import { ModelSection, OverviewSection } from '../../types'
import { Layout, BreadcrumbItem } from '../layout'
import {
SectionAuthorizedGuard,
Expand Down Expand Up @@ -205,6 +205,9 @@ const schemaSectionRoutes = Object.values(SECTIONS_MAP).map((section) => (
<BreadcrumbItem
label={section.titlePlural}
to={matchInfo.pathname}
learnMoreUrl={
(section as ModelSection).learnMoreUrl
}
/>
),
} satisfies RouteHandle
Expand Down
5 changes: 5 additions & 0 deletions src/pages/overview/card/SummaryCard.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,8 @@
.cardActions a {
text-decoration: none;
}

.learnMoreIconLink {
margin-left: auto;
place-self: center;
}
15 changes: 14 additions & 1 deletion src/pages/overview/card/SummaryCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import i18n from '@dhis2/d2-i18n'
import { Card, Button } from '@dhis2/ui'
import { IconEdit24 } from '@dhis2/ui-icons'
import { IconEdit24, IconQuestion16 } from '@dhis2/ui-icons'
import React, { PropsWithChildren } from 'react'
import { Link } from 'react-router-dom'
import {
Expand Down Expand Up @@ -92,6 +92,7 @@ export const SummaryCardActions = ({
hideNew,
}: SummaryCardActionsProps) => {
const canCreate = useCanCreateModelInSection(section)
const learnMoreUrl = section.learnMoreUrl
return (
<div className={styles.cardActions}>
{canCreate && !hideNew && (
Expand All @@ -106,6 +107,18 @@ export const SummaryCardActions = ({
{i18n.t('Manage')}
</Button>
</Link>
{learnMoreUrl && (
<a
className={styles.learnMoreIconLink}
href={learnMoreUrl}
target="_blank"
rel="noopener noreferrer"
>
<span aria-label={i18n.t('Learn more')}>
<IconQuestion16 />
</span>
</a>
)}
</div>
)
}
1 change: 1 addition & 0 deletions src/types/section.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface SectionBase {
minApiVersion?: number
maxApiVersion?: number
clonable?: boolean
learnMoreUrl?: string
}

// SchemaSection is a section that can be mapped directly to a schema by the name
Expand Down
Loading