Skip to content

Commit

Permalink
feat: APP-3051 - Implement Breadcrumbs module component (#141)
Browse files Browse the repository at this point in the history
  • Loading branch information
thekidnamedkd authored Apr 17, 2024
1 parent 45b01ac commit d06e4f7
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 6 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Added

- Implement `StateSkeletonBar` & `StateSkeletonCircular` core components
- Implement `Breadcrumbs`, `StateSkeletonBar`, and `StateSkeletonCircular` core components
- Added `slash` icon file

### Changed

- Update minor and patch dependencies
- Update `@testing-library/react` to v15
- Adjusted active and hover start styling on `AssetTransfer` module component

## [1.0.22] - 2024-04-12

Expand Down
1 change: 1 addition & 0 deletions src/core/assets/icons/slash.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions src/core/components/breadcrumbs/breadcrumbs.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Breadcrumbs } from './breadcrumbs';

const meta: Meta<typeof Breadcrumbs> = {
title: 'Core/Components/Breadcrumbs',
component: Breadcrumbs,
tags: ['autodocs'],
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/ISSDryshtEpB7SUSdNqAcw/branch/P0GeJKqILL7UXvaqu5Jj7V/Aragon-ODS?type=design&node-id=15704%3A53630&mode=design&t=wK3Bn7hqwwBM55IZ-1',
},
},
};

type Story = StoryObj<typeof Breadcrumbs>;

/**
* Default usage example of the Breadcrumb component.
*/
export const Default: Story = {
args: {
links: [{ label: 'Root', href: '/' }],
},
};

/**
* Usage example of the Breadcrumb component with full props.
*/
export const Loaded: Story = {
args: {
links: [
{ label: 'Root', href: '/' },
{ label: 'Page', href: '/page' },
{ label: 'Subpage', href: '/page/subpage' },
{ label: 'Current page', href: '/page/subpage/current' },
],
tag: { label: 'Tag', variant: 'info' },
},
};

export default meta;
50 changes: 50 additions & 0 deletions src/core/components/breadcrumbs/breadcrumbs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { render, screen } from '@testing-library/react';
import { Breadcrumbs, type IBreadcrumbsProps } from './breadcrumbs'; // Adjust the import path as necessary

describe('<Breadcrumbs /> component', () => {
const createTestComponent = (props?: Partial<IBreadcrumbsProps>) => {
const completeProps: IBreadcrumbsProps = {
links: [{ href: '/', label: 'Root' }],
...props,
};

return <Breadcrumbs {...completeProps} />;
};

it('renders all provided path links', () => {
const links = [
{ href: '/', label: 'Root' },
{ href: '/page', label: 'Page' },
{ href: '/page/subpage', label: 'Subpage' },
{ href: '/page/subpage/current/', label: 'Current page' },
];
render(createTestComponent({ links }));

const renderedLinks = screen.getAllByRole('link');
expect(renderedLinks.length).toBe(3);
expect(renderedLinks[0]).toHaveTextContent('Root');
expect(renderedLinks[1]).toHaveTextContent('Page');
expect(renderedLinks[2]).toHaveTextContent('Subpage');
});

it('displays the current location correctly', () => {
const links = [
{ label: 'Root', href: '/' },
{ label: 'This page', href: '/current' },
];
render(createTestComponent({ links }));

const currentPage = screen.getByText('This page');
expect(currentPage).toBeInTheDocument();
expect(currentPage).toHaveAttribute('aria-current', 'page');
expect(currentPage).not.toHaveAttribute('href');
});

it('renders with the Tag component when props provided', () => {
const tag = { label: 'Tag', variant: 'info' as const };
render(createTestComponent({ tag }));

const pillText = screen.getByText('Tag');
expect(pillText).toBeInTheDocument();
});
});
50 changes: 50 additions & 0 deletions src/core/components/breadcrumbs/breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Icon, IconType } from '../icon';
import { Link } from '../link';
import { Tag, type ITagProps } from '../tag';

export interface IBreadcrumbsLink {
/**
* Label to be displayed in the Breadcrumbs.
*/
label: string;
/**
* Optional href to be used in the Link component for clickable navigation.
*/
href?: string;
}

export interface IBreadcrumbsProps {
/**
* Array of BreadcrumbsLink objects `{label: string, href?: string}`
* The array indicates depth from the current position to be displayed in the Breadcrumbs.
* Starting at index 0 you must define the root up to the current location.
* The final index which will render as non-active and without separator.
*/
links: IBreadcrumbsLink[];
/**
* Optional tag pill to be displayed at the end of the Breadcrumbs for extra info. @type ITagProps
*/
tag?: ITagProps;
}

export const Breadcrumbs: React.FC<IBreadcrumbsProps> = ({ links, tag, ...otherProps }) => {
const currentPage = links[links.length - 1];
const pathLinks = links.slice(0, -1);

return (
<nav aria-label="Breadcrumbs" className="flex items-center gap-x-2" {...otherProps}>
<ol className="flex items-center gap-x-0.5">
{pathLinks.map((link) => (
<li key={link.href} className="flex items-center gap-x-1 whitespace-nowrap">
<Link href={link.href}>{link.label}</Link>
<Icon icon={IconType.SLASH} className="text-neutral-200" responsiveSize={{ md: 'lg' }} />
</li>
))}
<li aria-current="page" className="text-sm font-normal leading-tight text-neutral-500 md:text-base">
{currentPage.label}
</li>
</ol>
{tag && <Tag {...tag} />}
</nav>
);
};
1 change: 1 addition & 0 deletions src/core/components/breadcrumbs/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Breadcrumbs, type IBreadcrumbsLink, type IBreadcrumbsProps } from './breadcrumbs';
2 changes: 2 additions & 0 deletions src/core/components/icon/iconList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import RichtextListUnordered from '../../assets/icons/richtext-list-unordered.sv
import Search from '../../assets/icons/search.svg';
import Settings from '../../assets/icons/settings.svg';
import Shrink from '../../assets/icons/shrink.svg';
import Slash from '../../assets/icons/slash.svg';
import SortAsc from '../../assets/icons/sort-asc.svg';
import SortDesc from '../../assets/icons/sort-desc.svg';
import Success from '../../assets/icons/success.svg';
Expand Down Expand Up @@ -121,6 +122,7 @@ export const iconList: Record<IconType, IconComponent> = {
[IconType.SEARCH]: Search,
[IconType.SETTINGS]: Settings,
[IconType.SHRINK]: Shrink,
[IconType.SLASH]: Slash,
[IconType.SORT_ASC]: SortAsc,
[IconType.SORT_DESC]: SortDesc,
[IconType.SUCCESS]: Success,
Expand Down
1 change: 1 addition & 0 deletions src/core/components/icon/iconType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export enum IconType {
SEARCH = 'SEARCH',
SETTINGS = 'SETTINGS',
SHRINK = 'SHRINK',
SLASH = 'SLASH',
SORT_ASC = 'SORT_ASC',
SORT_DESC = 'SORT_DESC',
SUCCESS = 'SUCCESS',
Expand Down
1 change: 1 addition & 0 deletions src/core/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './alerts';
export * from './avatars';
export * from './breadcrumbs';
export * from './button';
export * from './cards';
export * from './checkbox';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const AssetTransfer: React.FC<IAssetTransferProps> = (props) => {
'flex h-16 w-full items-center justify-between rounded-xl border border-neutral-100 bg-neutral-0 px-4', // base
'hover:border-neutral-200 hover:shadow-neutral-md', // hover
'focus:outline-none focus-visible:rounded-xl focus-visible:ring focus-visible:ring-primary focus-visible:ring-offset', // focus
'active:border-neutral-300', // active
'active:border-neutral-300 active:shadow-none', // active
'md:h-20 md:px-6', // responsive
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ export const AssetTransferAddress: React.FC<IAssetTransferAddressProps> = (props
target="_blank"
rel="noopener noreferrer"
className={classNames(
'flex h-20 items-center space-x-4 border-neutral-100 px-4', //base
'group flex h-20 items-center space-x-4 border-neutral-100 px-4', //base
'hover:border-neutral-200 hover:shadow-neutral-md', //hover
'focus:outline-none focus-visible:ring focus-visible:ring-primary focus-visible:ring-offset', //focus
'active:border-neutral-300', //active
'active:border-neutral-300 active:shadow-none', //active
'md:w-1/2 md:p-6', //responsive
{
'rounded-t-xl md:rounded-l-xl md:rounded-r-none': txRole === 'sender', // sender base
Expand All @@ -58,7 +58,7 @@ export const AssetTransferAddress: React.FC<IAssetTransferAddressProps> = (props
)}
>
<MemberAvatar
className="shrink-0"
className="shrink-0 group-hover:shadow-neutral-md group-active:shadow-none"
responsiveSize={{ md: 'md' }}
ensName={participant.name}
address={participant.address}
Expand All @@ -71,7 +71,11 @@ export const AssetTransferAddress: React.FC<IAssetTransferAddressProps> = (props
<span className="truncate text-sm font-normal leading-tight text-neutral-800 md:text-base">
{resolvedUserHandle}
</span>
<Icon icon={IconType.LINK_EXTERNAL} size="sm" className="float-right text-neutral-300" />
<Icon
icon={IconType.LINK_EXTERNAL}
size="sm"
className="float-right text-neutral-300 group-hover:text-primary-300 group-active:text-primary-400"
/>
</div>
</div>
</a>
Expand Down

0 comments on commit d06e4f7

Please sign in to comment.