aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/molecules/nav/breadcrumb.module.scss10
-rw-r--r--src/components/molecules/nav/breadcrumb.stories.tsx81
-rw-r--r--src/components/molecules/nav/breadcrumb.test.tsx16
-rw-r--r--src/components/molecules/nav/breadcrumb.tsx130
-rw-r--r--src/components/molecules/nav/index.ts1
-rw-r--r--src/components/organisms/nav/breadcrumbs/breadcrumbs.module.scss7
-rw-r--r--src/components/organisms/nav/breadcrumbs/breadcrumbs.stories.tsx55
-rw-r--r--src/components/organisms/nav/breadcrumbs/breadcrumbs.test.tsx22
-rw-r--r--src/components/organisms/nav/breadcrumbs/breadcrumbs.tsx66
-rw-r--r--src/components/organisms/nav/breadcrumbs/index.ts1
-rw-r--r--src/components/organisms/nav/index.ts1
-rw-r--r--src/components/templates/page/page-layout.module.scss4
-rw-r--r--src/components/templates/page/page-layout.tsx15
13 files changed, 163 insertions, 246 deletions
diff --git a/src/components/molecules/nav/breadcrumb.module.scss b/src/components/molecules/nav/breadcrumb.module.scss
deleted file mode 100644
index 6786896..0000000
--- a/src/components/molecules/nav/breadcrumb.module.scss
+++ /dev/null
@@ -1,10 +0,0 @@
-@use "../../../styles/abstracts/placeholders";
-
-.item {
- &:not(:last-of-type) {
- &::after {
- content: ">";
- margin-left: var(--spacing-2xs);
- }
- }
-}
diff --git a/src/components/molecules/nav/breadcrumb.stories.tsx b/src/components/molecules/nav/breadcrumb.stories.tsx
deleted file mode 100644
index b6dd619..0000000
--- a/src/components/molecules/nav/breadcrumb.stories.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { Breadcrumb } from './breadcrumb';
-
-/**
- * Breadcrumb - Storybook Meta
- */
-export default {
- title: 'Molecules/Navigation/Breadcrumb',
- component: Breadcrumb,
- argTypes: {
- className: {
- control: {
- type: 'text',
- },
- table: {
- category: 'Styles',
- },
- description: 'Set additional classnames to the nav element.',
- type: {
- name: 'string',
- required: false,
- },
- },
- itemClassName: {
- control: {
- type: 'text',
- },
- table: {
- category: 'Styles',
- },
- description: 'Set additional classnames to the breadcrumb items.',
- type: {
- name: 'string',
- required: false,
- },
- },
- items: {
- description: 'The breadcrumb items.',
- type: {
- name: 'object',
- required: true,
- value: {},
- },
- },
- },
-} as ComponentMeta<typeof Breadcrumb>;
-
-const Template: ComponentStory<typeof Breadcrumb> = (args) => (
- <Breadcrumb {...args} />
-);
-
-/**
- * Breadcrumb Stories - One item
- */
-export const OneItem = Template.bind({});
-OneItem.args = {
- items: [{ id: 'home', url: '#', name: 'Home' }],
-};
-
-/**
- * Breadcrumb Stories - Two items
- */
-export const TwoItems = Template.bind({});
-TwoItems.args = {
- items: [
- { id: 'home', url: '#', name: 'Home' },
- { id: 'blog', url: '#', name: 'Blog' },
- ],
-};
-
-/**
- * Breadcrumb Stories - Three items
- */
-export const ThreeItems = Template.bind({});
-ThreeItems.args = {
- items: [
- { id: 'home', url: '#', name: 'Home' },
- { id: 'blog', url: '#', name: 'Blog' },
- { id: 'post1', url: '#', name: 'A Post' },
- ],
-};
diff --git a/src/components/molecules/nav/breadcrumb.test.tsx b/src/components/molecules/nav/breadcrumb.test.tsx
deleted file mode 100644
index 8aa0d63..0000000
--- a/src/components/molecules/nav/breadcrumb.test.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { describe, expect, it } from '@jest/globals';
-import { render, screen } from '../../../../tests/utils';
-import { Breadcrumb, type BreadcrumbItem } from './breadcrumb';
-
-const items: BreadcrumbItem[] = [
- { id: 'home', url: '#', name: 'Home' },
- { id: 'blog', url: '#', name: 'Blog' },
- { id: 'post1', url: '#', name: 'A Post' },
-];
-
-describe('Breadcrumb', () => {
- it('renders a navigation', () => {
- render(<Breadcrumb items={items} />);
- expect(screen.getByRole('navigation')).toBeInTheDocument();
- });
-});
diff --git a/src/components/molecules/nav/breadcrumb.tsx b/src/components/molecules/nav/breadcrumb.tsx
deleted file mode 100644
index 51f4633..0000000
--- a/src/components/molecules/nav/breadcrumb.tsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import Script from 'next/script';
-import type { FC } from 'react';
-import { useIntl } from 'react-intl';
-import type {
- BreadcrumbList,
- ListItem as ListItemType,
- WithContext,
-} from 'schema-dts';
-import { settings } from '../../../utils/config';
-import { Link, List, ListItem } from '../../atoms';
-import styles from './breadcrumb.module.scss';
-
-export type BreadcrumbItem = {
- /**
- * The item id.
- */
- id: string;
- /**
- * The item URL.
- */
- url: string;
- /**
- * The item name.
- */
- name: string;
-};
-
-export type BreadcrumbProps = {
- /**
- * Set additional classnames to the nav element.
- */
- className?: string;
- /**
- * Set additional classnames to the breadcrumb items.
- */
- itemClassName?: string;
- /**
- * The breadcrumb items
- */
- items: BreadcrumbItem[];
-};
-
-/**
- * Breadcrumb component
- *
- * Render a breadcrumb navigation.
- */
-export const Breadcrumb: FC<BreadcrumbProps> = ({
- itemClassName = '',
- items,
- ...props
-}) => {
- const intl = useIntl();
-
- const ariaLabel = intl.formatMessage({
- defaultMessage: 'Breadcrumb',
- description: 'Breadcrumb: an accessible name for the breadcrumb nav.',
- id: '28nnDY',
- });
-
- /**
- * Retrieve the breadcrumb list items.
- *
- * @param {BreadcrumbItem[]} list - The breadcrumb items.
- * @returns {JSX.Element[]} The list items.
- */
- const getListItems = (list: BreadcrumbItem[]): JSX.Element[] =>
- list.map((item, index) => {
- const isLastItem = index === list.length - 1;
- const itemStyles = isLastItem
- ? `${styles.item} screen-reader-text`
- : styles.item;
-
- return (
- <ListItem key={item.id} className={`${itemStyles} ${itemClassName}`}>
- {isLastItem ? item.name : <Link href={item.url}>{item.name}</Link>}
- </ListItem>
- );
- });
-
- /**
- * Retrieve the breadcrumb list items with Schema.org format.
- *
- * @param {BreadcrumbItem[]} list - The breadcrumb items.
- * @returns {ListItemType[]} An array of list items using Schema.org format.
- */
- const getSchemaItems = (list: BreadcrumbItem[]): ListItemType[] => {
- const schemaItems: ListItemType[] = [];
-
- list.forEach((item, index) => {
- schemaItems.push({
- '@type': 'ListItem',
- position: index + 1,
- name: item.name,
- item: item.url,
- });
- });
-
- return schemaItems;
- };
-
- const schemaJsonLd: WithContext<BreadcrumbList> = {
- '@context': 'https://schema.org',
- '@type': 'BreadcrumbList',
- '@id': `${settings.url}/#breadcrumb`,
- itemListElement: getSchemaItems(items),
- };
-
- return (
- <>
- <Script
- dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
- id="schema-breadcrumb"
- type="application/ld+json"
- />
- <nav aria-label={ariaLabel} {...props}>
- <span className="screen-reader-text">
- {intl.formatMessage({
- defaultMessage: 'You are here:',
- description: 'Breadcrumb: You are here prefix',
- id: '16zl9Z',
- })}
- </span>
- <List hideMarker isInline isOrdered spacing="2xs">
- {getListItems(items)}
- </List>
- </nav>
- </>
- );
-};
diff --git a/src/components/molecules/nav/index.ts b/src/components/molecules/nav/index.ts
index 2f9b8e3..08e47b7 100644
--- a/src/components/molecules/nav/index.ts
+++ b/src/components/molecules/nav/index.ts
@@ -1,4 +1,3 @@
-export * from './breadcrumb';
export * from './nav-item';
export * from './nav-link';
export * from './nav-list';
diff --git a/src/components/organisms/nav/breadcrumbs/breadcrumbs.module.scss b/src/components/organisms/nav/breadcrumbs/breadcrumbs.module.scss
new file mode 100644
index 0000000..1be81c4
--- /dev/null
+++ b/src/components/organisms/nav/breadcrumbs/breadcrumbs.module.scss
@@ -0,0 +1,7 @@
+.wrapper {
+ width: fit-content;
+}
+
+.sep {
+ margin-inline-start: var(--spacing-xs);
+}
diff --git a/src/components/organisms/nav/breadcrumbs/breadcrumbs.stories.tsx b/src/components/organisms/nav/breadcrumbs/breadcrumbs.stories.tsx
new file mode 100644
index 0000000..4736b26
--- /dev/null
+++ b/src/components/organisms/nav/breadcrumbs/breadcrumbs.stories.tsx
@@ -0,0 +1,55 @@
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+import { Breadcrumbs } from './breadcrumbs';
+
+/**
+ * Breadcrumbs - Storybook Meta
+ */
+export default {
+ title: 'Organisms/Nav/Breadcrumbs',
+ component: Breadcrumbs,
+ argTypes: {
+ items: {
+ description: 'The breadcrumb items.',
+ type: {
+ name: 'object',
+ required: true,
+ value: {},
+ },
+ },
+ },
+} as ComponentMeta<typeof Breadcrumbs>;
+
+const Template: ComponentStory<typeof Breadcrumbs> = (args) => (
+ <Breadcrumbs {...args} />
+);
+
+/**
+ * Breadcrumbs Stories - One item
+ */
+export const OneItem = Template.bind({});
+OneItem.args = {
+ items: [{ id: 'home', url: '#', name: 'Home' }],
+};
+
+/**
+ * Breadcrumbs Stories - Two items
+ */
+export const TwoItems = Template.bind({});
+TwoItems.args = {
+ items: [
+ { id: 'home', url: '#', name: 'Home' },
+ { id: 'blog', url: '#', name: 'Blog' },
+ ],
+};
+
+/**
+ * Breadcrumbs Stories - Three items
+ */
+export const ThreeItems = Template.bind({});
+ThreeItems.args = {
+ items: [
+ { id: 'home', url: '#', name: 'Home' },
+ { id: 'blog', url: '#', name: 'Blog' },
+ { id: 'post1', url: '#', name: 'A Post' },
+ ],
+};
diff --git a/src/components/organisms/nav/breadcrumbs/breadcrumbs.test.tsx b/src/components/organisms/nav/breadcrumbs/breadcrumbs.test.tsx
new file mode 100644
index 0000000..40bb1b8
--- /dev/null
+++ b/src/components/organisms/nav/breadcrumbs/breadcrumbs.test.tsx
@@ -0,0 +1,22 @@
+import { describe, expect, it } from '@jest/globals';
+import { render, screen as rtlScreen } from '@testing-library/react';
+import { Breadcrumbs, type BreadcrumbsItem } from './breadcrumbs';
+
+const items: BreadcrumbsItem[] = [
+ { id: 'home', url: '#', name: 'Home' },
+ { id: 'blog', url: '#', name: 'Blog' },
+ { id: 'post1', url: '#', name: 'A Post' },
+];
+
+describe('Breadcrumbs', () => {
+ it('renders a list of items wrapped in a nav element', () => {
+ const ariaLabel = 'error tempore iure';
+
+ render(<Breadcrumbs aria-label={ariaLabel} items={items} />);
+
+ expect(rtlScreen.getByRole('navigation')).toHaveAccessibleName(ariaLabel);
+ expect(rtlScreen.getAllByRole('listitem')).toHaveLength(items.length);
+ // The last item should not be linked
+ expect(rtlScreen.getAllByRole('link')).toHaveLength(items.length - 1);
+ });
+});
diff --git a/src/components/organisms/nav/breadcrumbs/breadcrumbs.tsx b/src/components/organisms/nav/breadcrumbs/breadcrumbs.tsx
new file mode 100644
index 0000000..b6d3843
--- /dev/null
+++ b/src/components/organisms/nav/breadcrumbs/breadcrumbs.tsx
@@ -0,0 +1,66 @@
+import { type ForwardRefRenderFunction, forwardRef } from 'react';
+import { Nav, VisuallyHidden, type NavProps } from '../../../atoms';
+import { NavItem, NavLink, NavList } from '../../../molecules';
+import styles from './breadcrumbs.module.scss';
+
+export type BreadcrumbsItem = {
+ /**
+ * The item id.
+ */
+ id: string;
+ /**
+ * The item URL.
+ */
+ url: string;
+ /**
+ * The item name.
+ */
+ name: string;
+};
+
+export type BreadcrumbsProps = Omit<NavProps, 'children'> & {
+ /**
+ * The breadcrumbs items.
+ */
+ items: BreadcrumbsItem[];
+};
+
+const BreadcrumbsWithRef: ForwardRefRenderFunction<
+ HTMLElement,
+ BreadcrumbsProps
+> = ({ className = '', items, ...props }, ref) => {
+ const wrapperClass = `${styles.wrapper} ${className}`;
+ const sep = '>';
+
+ return (
+ <Nav {...props} className={wrapperClass} ref={ref}>
+ <NavList
+ isInline
+ isOrdered
+ // eslint-disable-next-line react/jsx-no-literals
+ spacing="xs"
+ >
+ {items.map((item, index) => {
+ const isLastItem = items.length === index + 1;
+
+ return (
+ <NavItem key={item.id}>
+ {isLastItem ? (
+ <VisuallyHidden>{item.name}</VisuallyHidden>
+ ) : (
+ <>
+ <NavLink href={item.url} label={item.name} />
+ <span aria-hidden className={styles.sep}>
+ {sep}
+ </span>
+ </>
+ )}
+ </NavItem>
+ );
+ })}
+ </NavList>
+ </Nav>
+ );
+};
+
+export const Breadcrumbs = forwardRef(BreadcrumbsWithRef);
diff --git a/src/components/organisms/nav/breadcrumbs/index.ts b/src/components/organisms/nav/breadcrumbs/index.ts
new file mode 100644
index 0000000..8d59853
--- /dev/null
+++ b/src/components/organisms/nav/breadcrumbs/index.ts
@@ -0,0 +1 @@
+export * from './breadcrumbs';
diff --git a/src/components/organisms/nav/index.ts b/src/components/organisms/nav/index.ts
index cb72765..ad899e0 100644
--- a/src/components/organisms/nav/index.ts
+++ b/src/components/organisms/nav/index.ts
@@ -1 +1,2 @@
+export * from './breadcrumbs';
export * from './pagination';
diff --git a/src/components/templates/page/page-layout.module.scss b/src/components/templates/page/page-layout.module.scss
index 09bb957..4615f60 100644
--- a/src/components/templates/page/page-layout.module.scss
+++ b/src/components/templates/page/page-layout.module.scss
@@ -6,13 +6,11 @@
@extend %grid;
grid-column: 1 / -1;
+ width: 100%;
padding: var(--spacing-md) 0;
> * {
grid-column: 2;
- }
-
- &__items {
font-size: var(--font-size-sm);
}
}
diff --git a/src/components/templates/page/page-layout.tsx b/src/components/templates/page/page-layout.tsx
index dbac43e..3fd5b02 100644
--- a/src/components/templates/page/page-layout.tsx
+++ b/src/components/templates/page/page-layout.tsx
@@ -14,8 +14,6 @@ import type { Approved, SendCommentInput, SingleComment } from '../../../types';
import { useIsMounted } from '../../../utils/hooks';
import { Heading, Notice, type NoticeKind, Sidebar } from '../../atoms';
import {
- Breadcrumb,
- type BreadcrumbItem,
PageFooter,
type PageFooterProps,
PageHeader,
@@ -27,6 +25,8 @@ import {
CommentsList,
type CommentsListProps,
TableOfContents,
+ Breadcrumbs,
+ type BreadcrumbsItem,
} from '../../organisms';
import styles from './page-layout.module.scss';
@@ -62,7 +62,7 @@ export type PageLayoutProps = {
/**
* The breadcrumb items.
*/
- breadcrumb: BreadcrumbItem[];
+ breadcrumb: BreadcrumbsItem[];
/**
* The breadcrumb JSON schema.
*/
@@ -127,6 +127,11 @@ export const PageLayout: FC<PageLayoutProps> = ({
withToC = false,
}) => {
const intl = useIntl();
+ const breadcrumbsLabel = intl.formatMessage({
+ defaultMessage: 'Breadcrumb',
+ description: 'PageLayout: an accessible name for the breadcrumb nav.',
+ id: 'm6a3BD',
+ });
const commentsTitle = intl.formatMessage({
defaultMessage: 'Comments',
description: 'PageLayout: comments title',
@@ -211,9 +216,9 @@ export const PageLayout: FC<PageLayoutProps> = ({
id="schema-breadcrumb"
type="application/ld+json"
/>
- <Breadcrumb
+ <Breadcrumbs
+ aria-label={breadcrumbsLabel}
className={styles.breadcrumb}
- itemClassName={styles.breadcrumb__items}
items={breadcrumb}
/>
<PageHeader