diff options
Diffstat (limited to 'src/components/molecules')
| -rw-r--r-- | src/components/molecules/nav/breadcrumb.module.scss | 19 | ||||
| -rw-r--r-- | src/components/molecules/nav/breadcrumb.stories.tsx | 48 | ||||
| -rw-r--r-- | src/components/molecules/nav/breadcrumb.test.tsx | 15 | ||||
| -rw-r--r-- | src/components/molecules/nav/breadcrumb.tsx | 113 | 
4 files changed, 195 insertions, 0 deletions
| diff --git a/src/components/molecules/nav/breadcrumb.module.scss b/src/components/molecules/nav/breadcrumb.module.scss new file mode 100644 index 0000000..c26f60a --- /dev/null +++ b/src/components/molecules/nav/breadcrumb.module.scss @@ -0,0 +1,19 @@ +@use "@styles/abstracts/placeholders"; + +.list { +  @extend %reset-ordered-list; + +  display: flex; +  flex-flow: row wrap; +  align-items: center; +  gap: var(--spacing-2xs); +} + +.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 new file mode 100644 index 0000000..d283619 --- /dev/null +++ b/src/components/molecules/nav/breadcrumb.stories.tsx @@ -0,0 +1,48 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { IntlProvider } from 'react-intl'; +import BreadcrumbComponent, { type BreadcrumbItem } from './breadcrumb'; + +export default { +  title: 'Molecules/Nav', +  component: BreadcrumbComponent, +  argTypes: { +    className: { +      control: { +        type: 'text', +      }, +      table: { +        category: 'Styles', +      }, +      description: 'Set additional classnames to the nav element.', +      type: { +        name: 'string', +        required: false, +      }, +    }, +    items: { +      description: 'The breadcrumb items.', +      type: { +        name: 'object', +        required: true, +        value: {}, +      }, +    }, +  }, +} as ComponentMeta<typeof BreadcrumbComponent>; + +const Template: ComponentStory<typeof BreadcrumbComponent> = (args) => ( +  <IntlProvider locale="en"> +    <BreadcrumbComponent {...args} /> +  </IntlProvider> +); + +const items: BreadcrumbItem[] = [ +  { id: 'home', url: '#', name: 'Home' }, +  { id: 'blog', url: '#', name: 'Blog' }, +  { id: 'post1', url: '#', name: 'A Post' }, +]; + +export const Breadcrumb = Template.bind({}); +Breadcrumb.args = { +  items, +}; diff --git a/src/components/molecules/nav/breadcrumb.test.tsx b/src/components/molecules/nav/breadcrumb.test.tsx new file mode 100644 index 0000000..43220c9 --- /dev/null +++ b/src/components/molecules/nav/breadcrumb.test.tsx @@ -0,0 +1,15 @@ +import { render, screen } from '@test-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 new file mode 100644 index 0000000..33af735 --- /dev/null +++ b/src/components/molecules/nav/breadcrumb.tsx @@ -0,0 +1,113 @@ +import Link from '@components/atoms/links/link'; +import { settings } from '@utils/config'; +import Script from 'next/script'; +import { VFC } from 'react'; +import { useIntl } from 'react-intl'; +import { BreadcrumbList, ListItem, WithContext } from 'schema-dts'; +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; +  /** +   * The breadcrumb items +   */ +  items: BreadcrumbItem[]; +}; + +/** + * Breadcrumb component + * + * Render a breadcrumb navigation. + */ +const Breadcrumb: VFC<BreadcrumbProps> = ({ items, ...props }) => { +  const intl = useIntl(); + +  /** +   * Retrieve the breadcrumb list items. +   * +   * @param {BreadcrumbItem[]} list - The breadcrumb items. +   * @returns {JSX.Element[]} The list items. +   */ +  const getListItems = (list: BreadcrumbItem[]): JSX.Element[] => { +    return list.map((item, index) => { +      const isLastItem = index === list.length - 1; +      const itemClassnames = isLastItem +        ? `${styles.item} screen-reader-text` +        : styles.item; + +      return ( +        <li key={item.id} className={itemClassnames}> +          {isLastItem ? item.name : <Link href={item.url}>{item.name}</Link>} +        </li> +      ); +    }); +  }; + +  /** +   * Retrieve the breadcrumb list items with Schema.org format. +   * +   * @param {BreadcrumbItem[]} list - The breadcrumb items. +   * @returns {ListItem[]} An array of list items using Schema.org format. +   */ +  const getSchemaItems = (list: BreadcrumbItem[]): ListItem[] => { +    const schemaItems: ListItem[] = []; + +    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 +        id="schema-breadcrumb" +        type="application/ld+json" +        dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }} +      /> +      <nav {...props}> +        <span className="screen-reader-text"> +          {intl.formatMessage({ +            defaultMessage: 'You are here:', +            description: 'Breadcrumb: You are here prefix', +            id: '16zl9Z', +          })} +        </span> +        <ol className={styles.list}>{getListItems(items)}</ol> +      </nav> +    </> +  ); +}; + +export default Breadcrumb; | 
