From 7ef85bd63a96293c7f4a51dd3714e9ec6483ab1b Mon Sep 17 00:00:00 2001 From: Sumit6307 Date: Fri, 30 Jan 2026 14:40:41 +0530 Subject: [PATCH] feat: add Breadcrumbs component --- .../components/Breadcrumbs/BreadcrumbItem.tsx | 36 +++++++++++++ .../Breadcrumbs/Breadcrumbs.spec.tsx | 33 ++++++++++++ .../Breadcrumbs/Breadcrumbs.stories.tsx | 25 +++++++++ .../Breadcrumbs/Breadcrumbs.styles.scss | 51 +++++++++++++++++++ .../components/Breadcrumbs/Breadcrumbs.tsx | 39 ++++++++++++++ .../src/components/Breadcrumbs/index.ts | 2 + packages/fuselage/src/components/index.ts | 2 + 7 files changed, 188 insertions(+) create mode 100644 packages/fuselage/src/components/Breadcrumbs/BreadcrumbItem.tsx create mode 100644 packages/fuselage/src/components/Breadcrumbs/Breadcrumbs.spec.tsx create mode 100644 packages/fuselage/src/components/Breadcrumbs/Breadcrumbs.stories.tsx create mode 100644 packages/fuselage/src/components/Breadcrumbs/Breadcrumbs.styles.scss create mode 100644 packages/fuselage/src/components/Breadcrumbs/Breadcrumbs.tsx create mode 100644 packages/fuselage/src/components/Breadcrumbs/index.ts diff --git a/packages/fuselage/src/components/Breadcrumbs/BreadcrumbItem.tsx b/packages/fuselage/src/components/Breadcrumbs/BreadcrumbItem.tsx new file mode 100644 index 0000000000..e570f59ba5 --- /dev/null +++ b/packages/fuselage/src/components/Breadcrumbs/BreadcrumbItem.tsx @@ -0,0 +1,36 @@ +import type { ComponentProps, Ref } from 'react'; +import { forwardRef } from 'react'; + +import { Box } from '../Box'; + +type BreadcrumbItemProps = ComponentProps & { + selected?: boolean; + href?: string; + target?: string; + title?: string; +}; + +const BreadcrumbItem = forwardRef(function BreadcrumbItem( + { children, selected, href, target, title, ...props }: BreadcrumbItemProps, + ref: Ref, +) { + return ( + + + {children} + + + ); +}); + +export default BreadcrumbItem; diff --git a/packages/fuselage/src/components/Breadcrumbs/Breadcrumbs.spec.tsx b/packages/fuselage/src/components/Breadcrumbs/Breadcrumbs.spec.tsx new file mode 100644 index 0000000000..a7736bbfd1 --- /dev/null +++ b/packages/fuselage/src/components/Breadcrumbs/Breadcrumbs.spec.tsx @@ -0,0 +1,33 @@ +import { composeStories } from '@storybook/react-webpack5'; +import { axe } from 'jest-axe'; +import { SSRProvider } from 'react-aria'; + +import { render } from '../../testing'; + +import * as stories from './Breadcrumbs.stories'; + +const testCases = Object.values(composeStories(stories)).map((Story) => [ + Story.storyName || 'Story', + Story, +]); + +describe('[Breadcrumbs Component]', () => { + test.each(testCases)( + `renders %s without crashing`, + async (_storyname, Story) => { + const tree = render(, { + wrapper: ({ children }) => {children}, + }); + expect(tree.baseElement).toMatchSnapshot(); + }, + ); + + test.each(testCases)( + '%s should have no a11y violations', + async (_storyname, Story) => { + const { container } = render(); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }, + ); +}); diff --git a/packages/fuselage/src/components/Breadcrumbs/Breadcrumbs.stories.tsx b/packages/fuselage/src/components/Breadcrumbs/Breadcrumbs.stories.tsx new file mode 100644 index 0000000000..f4ab5a2925 --- /dev/null +++ b/packages/fuselage/src/components/Breadcrumbs/Breadcrumbs.stories.tsx @@ -0,0 +1,25 @@ +import type { StoryFn, Meta } from '@storybook/react-webpack5'; + +import BreadcrumbItem from './BreadcrumbItem'; +import Breadcrumbs from './Breadcrumbs'; + +export default { + title: 'Navigation/Breadcrumbs', + component: Breadcrumbs, +} satisfies Meta; + +export const Default: StoryFn = (props) => ( + + Home + Components + Breadcrumbs + +); + +export const WithTitle: StoryFn = (props) => ( + + Home + Components + Breadcrumbs + +); diff --git a/packages/fuselage/src/components/Breadcrumbs/Breadcrumbs.styles.scss b/packages/fuselage/src/components/Breadcrumbs/Breadcrumbs.styles.scss new file mode 100644 index 0000000000..835f1d28ab --- /dev/null +++ b/packages/fuselage/src/components/Breadcrumbs/Breadcrumbs.styles.scss @@ -0,0 +1,51 @@ +@use '../../styles/colors.scss'; +@use '../../styles/lengths.scss'; +@use '../../styles/typography.scss'; + +.rcx-breadcrumbs { + @include typography.use-font-scale(p2); + display: flex; + flex-flow: row nowrap; + align-items: center; + + color: colors.font(default); + + &__list { + display: flex; + flex-flow: row nowrap; + align-items: center; + margin: 0; + padding: 0; + list-style: none; + } + + &__item { + display: flex; + align-items: center; + white-space: nowrap; + + &-link { + color: colors.font(hint); + text-decoration: none; + cursor: pointer; + + &:hover, + &:focus { + color: colors.font(default); + } + + &--selected { + color: colors.font(default); + cursor: default; + pointer-events: none; + } + } + } + + &__separator { + display: flex; + align-items: center; + margin: 0 lengths.margin(8); + color: colors.font(hint); + } +} diff --git a/packages/fuselage/src/components/Breadcrumbs/Breadcrumbs.tsx b/packages/fuselage/src/components/Breadcrumbs/Breadcrumbs.tsx new file mode 100644 index 0000000000..0285ca2731 --- /dev/null +++ b/packages/fuselage/src/components/Breadcrumbs/Breadcrumbs.tsx @@ -0,0 +1,39 @@ +import flattenChildren from 'react-keyed-flatten-children'; +import type { ComponentProps, ReactNode, Ref } from 'react'; +import { forwardRef, Children, Fragment } from 'react'; + +import { Box } from '../Box'; +import { Chevron } from '../Chevron'; + +type BreadcrumbsProps = ComponentProps & { + children?: ReactNode; +}; + +const Breadcrumbs = forwardRef(function Breadcrumbs( + { children, ...props }: BreadcrumbsProps, + ref: Ref, +) { + const childrenArray = flattenChildren(children); + + const separatedChildren = Children.map(childrenArray, (child, index) => { + if (index === childrenArray.length - 1) { + return child; + } + return ( + + {child} + + + ); + }); + + return ( + + + {separatedChildren} + + + ); +}); + +export default Breadcrumbs; diff --git a/packages/fuselage/src/components/Breadcrumbs/index.ts b/packages/fuselage/src/components/Breadcrumbs/index.ts new file mode 100644 index 0000000000..8d3fdf730e --- /dev/null +++ b/packages/fuselage/src/components/Breadcrumbs/index.ts @@ -0,0 +1,2 @@ +export { default as Breadcrumbs } from './Breadcrumbs'; +export { default as BreadcrumbItem } from './BreadcrumbItem'; diff --git a/packages/fuselage/src/components/index.ts b/packages/fuselage/src/components/index.ts index 2fdd2cc8e6..ac84c218ac 100644 --- a/packages/fuselage/src/components/index.ts +++ b/packages/fuselage/src/components/index.ts @@ -6,6 +6,8 @@ export * from './Avatar'; export * from './Badge'; export * from './Banner'; export * from './Box'; +export * from './Breadcrumbs'; + export * from './Bubble'; export * from './Button'; export * from './ButtonGroup';