Skip to content

Commit

Permalink
feat(Breadcrumbs): add renderItem property (#1413)
Browse files Browse the repository at this point in the history
  • Loading branch information
GermanVor authored Mar 14, 2024
1 parent c787b5b commit d1c800f
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 36 deletions.
40 changes: 21 additions & 19 deletions src/components/Breadcrumbs/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ import type {PopupPlacement} from '../Popup';
import type {QAProps} from '../types';
import {block} from '../utils/cn';

import type {Props as BreadcrumbsItemProps} from './BreadcrumbsItem';
import {BreadcrumbsItem as Item} from './BreadcrumbsItem';
import {BreadcrumbsMore} from './BreadcrumbsMore';
import {BreadcrumbsSeparator} from './BreadcrumbsSeparator';
import type {
RenderBreadcrumbsItem,
RenderBreadcrumbsItemContent,
RenderBreadcrumbsRootContent,
} from './types';

import './Breadcrumbs.scss';

Expand All @@ -33,9 +39,10 @@ export type BreadcrumbsItem = LinkBreadcrumbsItem | ButtonBreadcrumbsItem;
export interface BreadcrumbsProps<T extends BreadcrumbsItem = BreadcrumbsItem> extends QAProps {
items: T[];
className?: string;
renderRootContent?: (item: T, isCurrent: boolean) => React.ReactNode;
renderItemContent?: (item: T, isCurrent: boolean, isPrevCurrent: boolean) => React.ReactNode;
renderRootContent?: RenderBreadcrumbsRootContent<T>;
renderItemContent?: RenderBreadcrumbsItemContent<T>;
renderItemDivider?: () => React.ReactNode;
renderItem?: RenderBreadcrumbsItem<T>;
lastDisplayedItemsCount: LastDisplayedItemsCount;
firstDisplayedItemsCount: FirstDisplayedItemsCount;
popupStyle?: 'staircase';
Expand Down Expand Up @@ -131,28 +138,30 @@ export class Breadcrumbs<T extends BreadcrumbsItem = BreadcrumbsItem> extends Re
const {className, qa} = this.props;
const {calculated} = this.state;

const rootItem = this.renderRootItem();

return (
<div className={b({calculated: calculated ? 'yes' : 'no'}, className)} data-qa={qa}>
<div className={b('inner')} ref={this.container}>
{rootItem}
{this.renderRootItem()}
{this.renderMoreItem()}
{this.renderVisibleItems()}
</div>
</div>
);
}

renderItem(data: T, isCurrent: boolean, isPrevCurrent: boolean) {
const {renderItemContent} = this.props;

renderItem(
item: T,
isCurrent: boolean,
isPrevCurrent: boolean,
renderItemContent?: BreadcrumbsItemProps<T>['renderItemContent'],
) {
return (
<Item
data={data}
item={item}
isCurrent={isCurrent}
isPrevCurrent={isPrevCurrent}
renderItem={renderItemContent}
renderItemContent={renderItemContent || this.props.renderItemContent}
renderItem={this.props.renderItem}
/>
);
}
Expand All @@ -164,22 +173,15 @@ export class Breadcrumbs<T extends BreadcrumbsItem = BreadcrumbsItem> extends Re
}

renderRootItem() {
const {renderRootContent, renderItemContent} = this.props;
const {renderRootContent} = this.props;
const {rootItem, visibleItems} = this.state;
const isCurrent = visibleItems.length === 0;

if (!rootItem) {
return null;
}

return (
<Item
data={rootItem}
isCurrent={isCurrent}
isPrevCurrent={false}
renderItem={renderRootContent || renderItemContent}
/>
);
return this.renderItem(rootItem, isCurrent, false, renderRootContent);
}

renderVisibleItems() {
Expand Down
45 changes: 28 additions & 17 deletions src/components/Breadcrumbs/BreadcrumbsItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,67 @@ import React from 'react';
import {Link} from '../Link';
import {block} from '../utils/cn';

import type {BreadcrumbsProps, BreadcrumbsItem as IBreadcrumbsItem} from './Breadcrumbs';
import type {BreadcrumbsItem as IBreadcrumbsItem} from './Breadcrumbs';
import {BreadcrumbsButton} from './BreadcrumbsButton';
import type {
RenderBreadcrumbsItem,
RenderBreadcrumbsItemContent,
RenderBreadcrumbsRootContent,
} from './types';

interface Props<T extends IBreadcrumbsItem = IBreadcrumbsItem> {
data: T;
export interface Props<T extends IBreadcrumbsItem = IBreadcrumbsItem> {
item: T;
isCurrent: boolean;
isPrevCurrent: boolean;
renderItem?:
| BreadcrumbsProps<T>['renderItemContent']
| BreadcrumbsProps<T>['renderRootContent'];
renderItemContent?: RenderBreadcrumbsItemContent<T> | RenderBreadcrumbsRootContent<T>;
renderItem?: RenderBreadcrumbsItem<T>;
}

const b = block('breadcrumbs');

function Item<T extends IBreadcrumbsItem = IBreadcrumbsItem>({
data,
item,
isCurrent,
isPrevCurrent,
renderItemContent,
renderItem,
}: Props<T>) {
const itemTitle = data.title || data.text;
const children = renderItemContent
? renderItemContent(item, isCurrent, isPrevCurrent)
: item.text;

const item = renderItem ? renderItem(data, isCurrent, isPrevCurrent) : data.text;
if (renderItem) {
return renderItem({item, children, isCurrent, isPrevCurrent});
}

const itemTitle = item.title || item.text;

if (isPrevCurrent || !isCurrent) {
if (data.href !== undefined) {
if (item.href !== undefined) {
return (
<Link
key={data.text}
key={item.text}
view="secondary"
href={data.href}
href={item.href}
title={itemTitle}
onClick={data.action}
onClick={item.action}
className={b('item', {'prev-current': isPrevCurrent})}
>
{item}
{children}
</Link>
);
}

return (
<BreadcrumbsButton key={data.text} title={itemTitle} onClick={data.action}>
{item}
<BreadcrumbsButton key={item.text} title={itemTitle} onClick={item.action}>
{children}
</BreadcrumbsButton>
);
}

return (
<div title={itemTitle} className={b('item', {current: true})}>
{item}
{children}
</div>
);
}
Expand Down
10 changes: 10 additions & 0 deletions src/components/Breadcrumbs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,11 +340,21 @@ return (
| renderRootContent | Custom render function of first item | `((item: BreadcrumbsItem, isCurrent: boolean) => React.ReactNode) \| undefined` | |
| renderItemContent | Custom render function of N+1 item | `((item: BreadcrumbsItem, isCurrent: boolean, isPrevCurrent: boolean) => React.ReactNode) \| undefined` | |
| renderItemDivider | Custom render function of items separator | `(() => React.ReactNode) \| undefined` | |
| renderItem | Custom render function of items | `(props: RenderBreadcrumbsItemProps<T>) => React.ReactNode) \| undefined` | |
| firstDisplayedItemsCount | Number of items to display before item collapse control | `FirstDisplayedItemsCount.Zero \| FirstDisplayedItemsCount.One` | |
| lastDisplayedItemsCount | Number of items to display after item collapse control | `LastDisplayedItemsCount.One \| LastDisplayedItemsCount.Two` | |
| popupStyle | Style of collapsed item popup | `"staircase" \| undefined` | |
| qa | HTML `data-qa` attribute, used in tests | `string` | |

### RenderBreadcrumbsItemProps<T>

| Name | Type |
| :------------ | :---------------: |
| children | `React.ReactNode` |
| item | `T` |
| isCurrent | `boolean` |
| isPrevCurrent | `boolean` |

### BreadcrumbsItem

| Name | Description | Type | Default |
Expand Down
13 changes: 13 additions & 0 deletions src/components/Breadcrumbs/__stories__/BreadcrumbsShowcase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import {cn} from '../../utils/cn';
import {Breadcrumbs} from '../Breadcrumbs';
import type {BreadcrumbsProps} from '../Breadcrumbs';
import type {RenderBreadcrumbsItemProps} from '../types';

import './BreadcrumbsShowcase.scss';

Expand Down Expand Up @@ -58,6 +59,10 @@ const sizes = [100, 150, 200, 250, 300];

interface BreadcrumbsShowcaseProps extends Omit<BreadcrumbsProps, 'items'> {}

const Container = ({children, isCurrent}: RenderBreadcrumbsItemProps) => {
return <div style={isCurrent ? undefined : {border: '1px solid tomato'}}>{children}</div>;
};

export function BreadcrumbsShowcase(props: BreadcrumbsShowcaseProps) {
const defaultBreadcrumbsList = sizes.map((size) => (
<div
Expand Down Expand Up @@ -96,6 +101,14 @@ export function BreadcrumbsShowcase(props: BreadcrumbsShowcaseProps) {
}))}
/>
</div>
<div className={b('item')}>
<p>Custom render item</p>
<Breadcrumbs
{...props}
items={breadcrumbsItems.map(({text}) => ({text, action: () => {}}))}
renderItem={Container}
/>
</div>
</div>
);
}
17 changes: 17 additions & 0 deletions src/components/Breadcrumbs/__tests__/Breadcrumbs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,20 @@ test('should display custom title', () => {
expect(screen.getByTitle('Custom title for Root')).toBeInTheDocument();
expect(screen.getByTitle('Custom title for Street')).toBeInTheDocument();
});

test('renderItem property', () => {
const getText = (text: string) => `qwerty_${text}`;

render(
<Breadcrumbs
items={items}
firstDisplayedItemsCount={0}
lastDisplayedItemsCount={1}
renderItem={({item}) => <div>{getText(item.text)}</div>}
/>,
);

items.forEach(({text}) => {
expect(screen.getByText(getText(text))).toBeInTheDocument();
});
});
23 changes: 23 additions & 0 deletions src/components/Breadcrumbs/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type {BreadcrumbsItem} from './Breadcrumbs';

export type RenderBreadcrumbsItemContent<T extends BreadcrumbsItem> = (
item: T,
isCurrent: boolean,
isPrevCurrent: boolean,
) => React.ReactNode;

export type RenderBreadcrumbsRootContent<T extends BreadcrumbsItem> = (
item: T,
isCurrent: boolean,
) => React.ReactNode;

export type RenderBreadcrumbsItemProps<T extends BreadcrumbsItem = BreadcrumbsItem> = {
children: React.ReactNode;
item: T;
isCurrent: boolean;
isPrevCurrent: boolean;
};

export type RenderBreadcrumbsItem<T extends BreadcrumbsItem> = (
props: RenderBreadcrumbsItemProps<T>,
) => React.ReactNode;

0 comments on commit d1c800f

Please sign in to comment.