forked from nodejs/nodejs.org
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(components): add
Breadcrumbs
component (nodejs#5928)
* feat(components): add `Breadcrumbs` component * feat(Storybook): add Breadcrumbs Hidden Home story * fix(Breadcrumbs): fix CSS indentation * fix(Breadcrumbs): fix typo * fix(Breadcrumbs): fix eslint import order * refactor(Breadcrumbs): split Breadcrumb into sub components * style(Breadcrumbs): remove comment and update component * fix(Breadcrumbs): use LocalizedLink * style(Breadcrumbs): remove abbreviation * refactor(Breadcrumbs): improve code readability * style(Breadcrumbs): format Breadcrumbs story * fix(Breadcrumbs): add default value for links prop * test(Breadcrumbs): add unit test * test(Breadcrumbs): remove unit test * feat(Breadcrumbs): add microdata * feat(Breadcrumbs): use intl for home aria label * fix(Breadcrumbs): itemProp without wrapping with span * refactor(Breadcrumbs): modularize Breadcrumb and refine logic * style(Breadcrumbs): rename other to props * fix(Breadcrumbs): move name to link * fix(Breadcrumbs): disable microdata for truncated item
- Loading branch information
Showing
12 changed files
with
319 additions
and
0 deletions.
There are no files selected for viewing
4 changes: 4 additions & 0 deletions
4
components/Common/Breadcrumbs/BreadcrumbHomeLink/index.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.icon { | ||
@apply h-4 | ||
w-4; | ||
} |
36 changes: 36 additions & 0 deletions
36
components/Common/Breadcrumbs/BreadcrumbHomeLink/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import HomeIcon from '@heroicons/react/24/outline/HomeIcon'; | ||
import type { ComponentProps, FC } from 'react'; | ||
import { useIntl } from 'react-intl'; | ||
|
||
import BreadcrumbLink from '@/components/Common/Breadcrumbs/BreadcrumbLink'; | ||
|
||
import styles from './index.module.css'; | ||
|
||
type BreadcrumbHomeLinkProps = Omit< | ||
ComponentProps<typeof BreadcrumbLink>, | ||
'href' | ||
> & | ||
Partial<Pick<ComponentProps<typeof BreadcrumbLink>, 'href'>>; | ||
|
||
const BreadcrumbHomeLink: FC<BreadcrumbHomeLinkProps> = ({ | ||
href = '/', | ||
...props | ||
}) => { | ||
const { formatMessage } = useIntl(); | ||
|
||
const navigateToHome = formatMessage({ | ||
id: 'components.common.breadcrumbs.navigateToHome', | ||
}); | ||
|
||
return ( | ||
<BreadcrumbLink href={href} {...props}> | ||
<HomeIcon | ||
title={navigateToHome} | ||
aria-label={navigateToHome} | ||
className={styles.icon} | ||
/> | ||
</BreadcrumbLink> | ||
); | ||
}; | ||
|
||
export default BreadcrumbHomeLink; |
20 changes: 20 additions & 0 deletions
20
components/Common/Breadcrumbs/BreadcrumbItem/index.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
.item { | ||
@apply flex | ||
items-center | ||
gap-5 | ||
text-sm | ||
font-medium | ||
text-neutral-800 | ||
dark:text-neutral-200; | ||
|
||
&.visuallyHidden { | ||
@apply hidden; | ||
} | ||
|
||
.separator { | ||
@apply h-4 | ||
w-4 | ||
text-neutral-600 | ||
dark:text-neutral-400; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import ChevronRightIcon from '@heroicons/react/24/outline/ChevronRightIcon'; | ||
import classNames from 'classnames'; | ||
import type { ComponentProps, FC, PropsWithChildren } from 'react'; | ||
|
||
import styles from './index.module.css'; | ||
|
||
type BreadcrumbItemProps = { | ||
disableMicrodata?: boolean; | ||
hidden?: boolean; | ||
hideSeparator?: boolean; | ||
position?: number; | ||
} & ComponentProps<'li'>; | ||
|
||
const BreadcrumbItem: FC<PropsWithChildren<BreadcrumbItemProps>> = ({ | ||
disableMicrodata, | ||
children, | ||
hidden = false, | ||
hideSeparator = false, | ||
position, | ||
...props | ||
}) => ( | ||
<li | ||
{...props} | ||
itemProp={!disableMicrodata ? 'itemListElement' : undefined} | ||
itemScope={!disableMicrodata ? true : undefined} | ||
itemType={!disableMicrodata ? 'https://schema.org/ListItem' : undefined} | ||
className={classNames( | ||
styles.item, | ||
{ [styles.visuallyHidden]: hidden }, | ||
props.className | ||
)} | ||
aria-hidden={hidden ? 'true' : undefined} | ||
> | ||
{children} | ||
{position && <meta itemProp="position" content={`${position}`} />} | ||
{!hideSeparator && ( | ||
<ChevronRightIcon aria-hidden="true" className={styles.separator} /> | ||
)} | ||
</li> | ||
); | ||
|
||
export default BreadcrumbItem; |
8 changes: 8 additions & 0 deletions
8
components/Common/Breadcrumbs/BreadcrumbLink/index.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
.active { | ||
@apply rounded | ||
bg-green-600 | ||
px-2 | ||
py-1 | ||
font-semibold | ||
text-white; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import classNames from 'classnames'; | ||
import type { ComponentProps, FC } from 'react'; | ||
|
||
import LocalizedLink from '@/components/LocalizedLink'; | ||
|
||
import styles from './index.module.css'; | ||
|
||
type BreadcrumbLinkProps = { | ||
active?: boolean; | ||
} & ComponentProps<typeof LocalizedLink>; | ||
|
||
const BreadcrumbLink: FC<BreadcrumbLinkProps> = ({ | ||
href, | ||
active, | ||
...props | ||
}) => ( | ||
<LocalizedLink | ||
itemScope | ||
itemType="http://schema.org/Thing" | ||
itemProp="item" | ||
itemID={href.toString()} | ||
href={href} | ||
className={classNames({ [styles.active]: active }, props.className)} | ||
aria-current={active ? 'page' : undefined} | ||
{...props} | ||
> | ||
<span itemProp="name">{props.children}</span> | ||
</LocalizedLink> | ||
); | ||
|
||
export default BreadcrumbLink; |
5 changes: 5 additions & 0 deletions
5
components/Common/Breadcrumbs/BreadcrumbRoot/index.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.list { | ||
@apply flex | ||
items-center | ||
gap-5; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import type { FC, PropsWithChildren, ComponentProps } from 'react'; | ||
|
||
import styles from './index.module.css'; | ||
|
||
const BreadcrumbRoot: FC<PropsWithChildren<ComponentProps<'nav'>>> = ({ | ||
children, | ||
...props | ||
}) => ( | ||
<nav aria-label="breadcrumb" {...props}> | ||
<ol | ||
itemScope | ||
itemType="https://schema.org/BreadcrumbList" | ||
className={styles.list} | ||
> | ||
{children} | ||
</ol> | ||
</nav> | ||
); | ||
|
||
export default BreadcrumbRoot; |
9 changes: 9 additions & 0 deletions
9
components/Common/Breadcrumbs/BreadcrumbTruncatedItem/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import BreadcrumbItem from '@/components/Common/Breadcrumbs/BreadcrumbItem'; | ||
|
||
const BreadcrumbTruncatedItem = () => ( | ||
<BreadcrumbItem disableMicrodata> | ||
<button disabled>…</button> | ||
</BreadcrumbItem> | ||
); | ||
|
||
export default BreadcrumbTruncatedItem; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import type { Meta as MetaObj, StoryObj } from '@storybook/react'; | ||
|
||
import Breadcrumbs from './'; | ||
|
||
type Story = StoryObj<typeof Breadcrumbs>; | ||
type Meta = MetaObj<typeof Breadcrumbs>; | ||
|
||
export const Default: Story = { | ||
args: { | ||
links: [ | ||
{ | ||
label: 'Learn', | ||
href: '/learn', | ||
}, | ||
{ | ||
label: 'Getting Started', | ||
href: '/learn/getting-started', | ||
}, | ||
{ | ||
label: 'Introduction to Node.js', | ||
href: '/learn/getting-started/intro', | ||
}, | ||
], | ||
}, | ||
}; | ||
|
||
export const Truncate: Story = { | ||
args: { | ||
links: [ | ||
{ | ||
label: 'Learn', | ||
href: '/learn', | ||
}, | ||
{ | ||
label: 'Getting Started', | ||
href: '/learn/getting-started', | ||
}, | ||
{ | ||
label: 'Introduction to Node.js', | ||
href: '/learn/getting-started/intro', | ||
}, | ||
{ | ||
label: 'Installation', | ||
href: '/learn/getting-started/intro/installation', | ||
}, | ||
{ | ||
label: 'Documentation', | ||
href: '/learn/getting-started/intro/installation/documentation', | ||
}, | ||
], | ||
maxLength: 1, | ||
}, | ||
}; | ||
|
||
export const HiddenHome: Story = { | ||
args: { | ||
hideHome: true, | ||
links: [ | ||
{ | ||
label: 'Learn', | ||
href: '/learn', | ||
}, | ||
{ | ||
label: 'Getting Started', | ||
href: '/learn/getting-started', | ||
}, | ||
{ | ||
label: 'Introduction to Node.js', | ||
href: '/learn/getting-started/intro', | ||
}, | ||
], | ||
}, | ||
}; | ||
|
||
export default { component: Breadcrumbs } as Meta; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { type LinkProps } from 'next/link'; | ||
import { useMemo, type FC } from 'react'; | ||
|
||
import BreadcrumbHomeLink from './BreadcrumbHomeLink'; | ||
import BreadcrumbItem from './BreadcrumbItem'; | ||
import BreadcrumbLink from './BreadcrumbLink'; | ||
import BreadcrumbRoot from './BreadcrumbRoot'; | ||
import BreadcrumbTruncatedItem from './BreadcrumbTruncatedItem'; | ||
|
||
type BreadcrumbLink = { | ||
label: string; | ||
href: LinkProps['href']; | ||
}; | ||
|
||
type BreadcrumbsProps = { | ||
links: BreadcrumbLink[]; | ||
maxLength?: number; | ||
hideHome?: boolean; | ||
}; | ||
|
||
const Breadcrumbs: FC<BreadcrumbsProps> = ({ | ||
links = [], | ||
maxLength = 5, | ||
hideHome = false, | ||
}) => { | ||
const totalLength = links.length + +!hideHome; | ||
const lengthOffset = maxLength - totalLength; | ||
const isOverflow = lengthOffset < 0; | ||
|
||
const items = useMemo( | ||
() => | ||
links.map((link, index, items) => { | ||
const position = index + 1; | ||
const isLastItem = index === items.length - 1; | ||
const hidden = | ||
// We add 1 here to take into account of the truncated breadcrumb. | ||
position <= Math.abs(lengthOffset) + 1 && isOverflow && !isLastItem; | ||
|
||
return ( | ||
<BreadcrumbItem | ||
key={link.href.toString()} | ||
hidden={hidden} | ||
hideSeparator={isLastItem} | ||
position={position + +!hideHome} | ||
> | ||
<BreadcrumbLink href={link.href} active={isLastItem}> | ||
{link.label} | ||
</BreadcrumbLink> | ||
</BreadcrumbItem> | ||
); | ||
}), | ||
[hideHome, isOverflow, lengthOffset, links] | ||
); | ||
|
||
return ( | ||
<BreadcrumbRoot> | ||
{!hideHome && ( | ||
<BreadcrumbItem position={1}> | ||
<BreadcrumbHomeLink /> | ||
</BreadcrumbItem> | ||
)} | ||
{isOverflow && <BreadcrumbTruncatedItem />} | ||
{items} | ||
</BreadcrumbRoot> | ||
); | ||
}; | ||
|
||
export default Breadcrumbs; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters