Skip to content

Commit

Permalink
[EUI+] Implement Breadcrumbs design changes (#7885)
Browse files Browse the repository at this point in the history
  • Loading branch information
mgadewoll authored Jul 16, 2024
1 parent d405f1a commit c1437b7
Show file tree
Hide file tree
Showing 14 changed files with 851 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl';
import { translate } from '@docusaurus/Translate';
import { EuiIcon, useEuiMemoizedStyles } from '@elastic/eui';

import { getItemStyles } from '../item.styles';

export default function HomeBreadcrumbItem(): JSX.Element {
const homeHref = useBaseUrl('/');

const styles = useEuiMemoizedStyles(getItemStyles);

return (
<li className="breadcrumbs__item" css={styles.item}>
<Link
aria-label={translate({
id: 'theme.docs.breadcrumbs.home',
message: 'Home page',
description: 'The ARIA label for the home page in the breadcrumbs',
})}
className="breadcrumbs__link"
href={homeHref}
>
EUI
</Link>
<EuiIcon type="arrowRight" size="s" css={styles.icon} />
</li>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { css } from '@emotion/react';
import { UseEuiTheme } from '@elastic/eui';

export const getItemStyles = ({ euiTheme }: UseEuiTheme) => ({
item: css`
--ifm-breadcrumb-item-background: transparent;
--ifm-breadcrumb-item-background-active: transparent;
// overwrite global styles
&.breadcrumbs__item:not(:last-child):after {
content: none;
}
.breadcrumbs__link {
padding: 0 ${euiTheme.size.m};
font-size: var(--eui-font-size-xs);
line-height: var(--eui-line-height-m);
}
a.breadcrumbs__link {
color: ${euiTheme.colors.link};
font-weight: ${euiTheme.font.weight.bold};
text-decoration: underline;
}
&.breadcrumbs__item--active .breadcrumbs__link {
color: ${euiTheme.colors.text};
}
`,
icon: css`
block-size: ${euiTheme.size.s};
inline-size: ${euiTheme.size.s};
fill: ${euiTheme.colors.text};
`,
});
138 changes: 138 additions & 0 deletions packages/docusaurus-theme/src/theme/DocBreadcrumbs/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import React, { type ReactNode } from 'react';
import clsx from 'clsx';
import { css } from '@emotion/react';
import { ThemeClassNames } from '@docusaurus/theme-common';
import {
useSidebarBreadcrumbs,
useHomePageRoute,
} from '@docusaurus/theme-common/internal';
import Link from '@docusaurus/Link';
import { translate } from '@docusaurus/Translate';
import { EuiIcon, useEuiMemoizedStyles, UseEuiTheme } from '@elastic/eui';

import HomeBreadcrumbItem from './Items/Home';
import { getItemStyles } from './Items/item.styles';

// converted from css modules to Emotion
const getStyles = ({ euiTheme }: UseEuiTheme) => ({
breadcrumbsContainer: css`
--ifm-breadcrumb-size-multiplier: 0.8;
// align breadcrumb items with content
margin-inline-start: -${euiTheme.size.m};
`,
});

// TODO move to design system folder
function BreadcrumbsItemLink({
children,
href,
isLast,
}: {
children: ReactNode;
href: string | undefined;
isLast: boolean;
}): JSX.Element {
const className = 'breadcrumbs__link';
if (isLast) {
return (
<span className={className} itemProp="name">
{children}
</span>
);
}
return href ? (
<Link className={className} href={href} itemProp="item">
<span itemProp="name">{children}</span>
</Link>
) : (
// TODO Google search console doesn't like breadcrumb items without href.
// The schema doesn't seem to require `id` for each `item`, although Google
// insist to infer one, even if it's invalid. Removing `itemProp="item
// name"` for now, since I don't know how to properly fix it.
// See https://github.com/facebook/docusaurus/issues/7241
<span className={className}>{children}</span>
);
}

function BreadcrumbsItem({
children,
active,
index,
addMicrodata,
}: {
children: ReactNode;
active?: boolean;
index: number;
addMicrodata: boolean;
}): JSX.Element {
const styles = useEuiMemoizedStyles(getItemStyles);

return (
<li
{...(addMicrodata && {
itemScope: true,
itemProp: 'itemListElement',
itemType: 'https://schema.org/ListItem',
})}
className={clsx('breadcrumbs__item', {
'breadcrumbs__item--active': active,
})}
css={styles.item}
>
{children}
<meta itemProp="position" content={String(index + 1)} />
{!active && <EuiIcon type="arrowRight" css={styles.icon} />}
</li>
);
}

export default function DocBreadcrumbs(): JSX.Element | null {
const breadcrumbs = useSidebarBreadcrumbs();
const homePageRoute = useHomePageRoute();

const styles = useEuiMemoizedStyles(getStyles);

if (!breadcrumbs) {
return null;
}

return (
<nav
className={clsx(ThemeClassNames.docs.docBreadcrumbs)}
aria-label={translate({
id: 'theme.docs.breadcrumbs.navAriaLabel',
message: 'Breadcrumbs',
description: 'The ARIA label for the breadcrumbs',
})}
css={styles.breadcrumbsContainer}
>
<ul
className="breadcrumbs"
itemScope
itemType="https://schema.org/BreadcrumbList"
>
{homePageRoute && <HomeBreadcrumbItem />}
{breadcrumbs.map((item, idx) => {
const isLast = idx === breadcrumbs.length - 1;
const href =
item.type === 'category' && item.linkUnlisted
? undefined
: item.href;
return (
<BreadcrumbsItem
key={idx}
active={isLast}
index={idx}
addMicrodata={!!href}
>
<BreadcrumbsItemLink href={href} isLast={isLast}>
{item.label}
</BreadcrumbsItemLink>
</BreadcrumbsItem>
);
})}
</ul>
</nav>
);
}
6 changes: 3 additions & 3 deletions packages/docusaurus-theme/src/theme/DocItem/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { css } from '@emotion/react';
import { useWindowSize } from '@docusaurus/theme-common';
import { useDoc } from '@docusaurus/theme-common/internal';
import DocItemPaginator from '@theme-original/DocItem/Paginator';
import DocVersionBanner from '@theme-original/DocVersionBanner';
import DocVersionBadge from '@theme-original/DocVersionBadge';
import DocBreadcrumbs from '@theme-original/DocBreadcrumbs';
import Unlisted from '@theme-original/Unlisted';
import * as Props from '@theme-original/DocItem/Layout';
import { EuiHorizontalRule } from '@elastic/eui';

import DocVersionBanner from '../../DocVersionBanner';
import DocVersionBadge from '../../DocVersionBadge';
import DocBreadcrumbs from '../../DocBreadcrumbs';
import DocItemContent from '../Content';
import DocItemTOCMobile from '../TOC/Mobile';
import DocItemTOCDesktop from '../TOC/Desktop';
Expand Down
2 changes: 1 addition & 1 deletion packages/docusaurus-theme/src/theme/DocItem/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';
import { HtmlClassNameProvider } from '@docusaurus/theme-common';
import { DocProvider } from '@docusaurus/theme-common/internal';
import DocItemMetadata from '@theme-original/DocItem/Metadata';
import type { Props } from '@theme/DocItem';

import DocItemMetadata from './Metadata';
import DocItemLayout from './Layout';

export default function DocItem(props: Props): JSX.Element {
Expand Down
53 changes: 53 additions & 0 deletions packages/docusaurus-theme/src/theme/DocRoot/Layout/Main/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import clsx from 'clsx';
import { css } from '@emotion/react';
import { useDocsSidebar } from '@docusaurus/theme-common/internal';
import type { Props } from '@theme-original/DocRoot/Layout/Main';

// converted from css modules to Emotion
const styles = {
docMainContainer: css`
display: flex;
width: 100%;
@media (min-width: 997px) {
flex-grow: 1;
max-width: calc(100% - var(--doc-sidebar-width));
}
`,
docMainContainerEnhanced: css`
@media (min-width: 997px) {
max-width: calc(100% - var(--doc-sidebar-hidden-width));
}
`,
docItemWrapperEnhanced: css`
@media (min-width: 997px) {
max-width: calc(
var(--ifm-container-width) + var(--doc-sidebar-width)
) !important;
}
`,
};

export default function DocRootLayoutMain({
hiddenSidebarContainer,
children,
}: any): JSX.Element {
const sidebar = useDocsSidebar();
return (
<main
className={clsx(styles.docMainContainer)}
css={[
styles.docMainContainer,
(hiddenSidebarContainer || !sidebar) && styles.docMainContainerEnhanced,
]}
>
<div
className="container padding-top--md padding-bottom--lg"
css={[hiddenSidebarContainer && styles.docItemWrapperEnhanced]}
>
{children}
</div>
</main>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';
import { css } from '@emotion/react';
import { translate } from '@docusaurus/Translate';
import IconArrow from '@theme-original/Icon/Arrow';

import type { Props } from '@theme-original/DocRoot/Layout/Sidebar/ExpandButton';

// converted from css modules to Emotion
const styles = {
expandButton: css`
@media (min-width: 997px) {
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
transition: background-color var(--ifm-transition-fast) ease;
background-color: var(--docusaurus-collapse-button-bg);
&:hover,
&:focus {
background-color: var(--docusaurus-collapse-button-bg-hover);
}
}
`,
expandButtonIcon: css`
transform: rotate(0);
`,
};

export default function DocRootLayoutSidebarExpandButton({
toggleSidebar,
}: Props): JSX.Element {
return (
<div
css={styles.expandButton}
title={translate({
id: 'theme.docs.sidebar.expandButtonTitle',
message: 'Expand sidebar',
description:
'The ARIA label and title attribute for expand button of doc sidebar',
})}
aria-label={translate({
id: 'theme.docs.sidebar.expandButtonAriaLabel',
message: 'Expand sidebar',
description:
'The ARIA label and title attribute for expand button of doc sidebar',
})}
tabIndex={0}
role="button"
onKeyDown={toggleSidebar}
onClick={toggleSidebar}
>
<IconArrow css={styles.expandButtonIcon} />
</div>
);
}
Loading

0 comments on commit c1437b7

Please sign in to comment.