Skip to content

Commit

Permalink
feat(Breadcrumbs): replace navigate with custom item component (#2054)
Browse files Browse the repository at this point in the history
  • Loading branch information
ValeraS authored and amje committed Jan 31, 2025
1 parent 0d88f12 commit 9a6966e
Show file tree
Hide file tree
Showing 19 changed files with 973 additions and 368 deletions.
96 changes: 0 additions & 96 deletions src/components/Breadcrumbs/BreadcrumbItem.tsx

This file was deleted.

37 changes: 17 additions & 20 deletions src/components/Breadcrumbs/Breadcrumbs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ $block: '.#{variables.$ns}breadcrumbs';
&:last-child {
font-weight: var(--g-text-accent-font-weight);
overflow: hidden;
margin: -2px;
padding: 2px;

#{$block}__link {
@include mixins.overflow-ellipsis();
Expand Down Expand Up @@ -65,6 +67,7 @@ $block: '.#{variables.$ns}breadcrumbs';

&:focus-visible {
outline: 2px solid var(--g-color-line-focus);
outline-offset: 0;
}
}

Expand All @@ -77,34 +80,28 @@ $block: '.#{variables.$ns}breadcrumbs';

&__more-button {
--g-button-border-radius: var(--g-focus-border-radius);
--g-button-focus-outline-offset: -2px;
}

&__menu {
margin-inline: calc(-1 * var(--g-spacing-2));
}

&__item:first-child &__menu {
margin-inline-start: 0;
}
&-popup {
--g-list-item-view-spacer-size: 8px;
max-width: 320px;
padding: var(--g-spacing-1);
}

&__popup_staircase {
$menu: '.#{variables.$ns}menu';
$staircaseLength: 10;
#{$menu} {
#{$menu}__list-item {
#{$menu}__item[class] {
padding-inline-start: 8px * $staircaseLength;
}
}
&-link {
text-decoration: none;
cursor: default;

@for $i from 0 through $staircaseLength {
#{$menu}__list-item:nth-child(#{$i}) {
#{$menu}__item[class] {
padding-inline-start: 8px * $i;
}
}
&:not([aria-disabled]) {
cursor: pointer;
}
}
}

&__item:first-child &__menu {
margin-inline-start: 0;
}
}
147 changes: 55 additions & 92 deletions src/components/Breadcrumbs/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,27 @@
import * as React from 'react';

import {useForkRef, useResizeObserver} from '../../hooks';
import {Button} from '../Button';
import {DropdownMenu} from '../DropdownMenu';
import type {PopupPlacement} from '../Popup';
import type {AriaLabelingProps, DOMProps, Href, Key, QAProps, RouterOptions} from '../types';
import type {AriaLabelingProps, DOMProps, Key, QAProps} from '../types';
import {filterDOMProps} from '../utils/filterDOMProps';

import {BreadcrumbItem} from './BreadcrumbItem';
import {BreadcrumbsDropdownMenu} from './BreadcrumbsDropdownMenu';
import {BreadcrumbsItem} from './BreadcrumbsItem';
import type {BreadcrumbsItemInnerProps} from './BreadcrumbsItem';
import {BreadcrumbsSeparator} from './BreadcrumbsSeparator';
import i18n from './i18n';
import {b, shouldClientNavigate} from './utils';
import {b} from './utils';

import './Breadcrumbs.scss';

export interface BreadcrumbsItemProps {
children: React.ReactNode;
title?: string;
href?: Href;
hrefLang?: string;
target?: React.HTMLAttributeAnchorTarget;
rel?: string;
download?: boolean | string;
ping?: string;
referrerPolicy?: React.HTMLAttributeReferrerPolicy;
'aria-label'?: string;
'aria-current'?: React.AriaAttributes['aria-current'];
routerOptions?: RouterOptions;
disabled?: boolean;
}

function Item(_props: BreadcrumbsItemProps): React.ReactElement | null {
return null;
}

export interface BreadcrumbsProps extends DOMProps, AriaLabelingProps, QAProps {
id?: string;
showRoot?: boolean;
separator?: React.ReactNode;
maxItems?: number;
popupStyle?: 'staircase';
popupPlacement?: PopupPlacement;
children: React.ReactElement<BreadcrumbsItemProps> | React.ReactElement<BreadcrumbsItemProps>[];
navigate?: (href: Href, routerOptions: RouterOptions | undefined) => void;
itemComponent?: React.ElementType;
children: React.ReactNode;
disabled?: boolean;
onAction?: (key: Key) => void;
}
Expand Down Expand Up @@ -155,7 +134,6 @@ export const Breadcrumbs = React.forwardRef(function Breadcrumbs(
}
});

const {navigate} = props;
let contents = items;
if (items.length > visibleItemsCount) {
contents = [];
Expand All @@ -170,61 +148,40 @@ export const Breadcrumbs = React.forwardRef(function Breadcrumbs(
}
const hiddenItems = breadcrumbs.slice(0, -endItems);
const menuItem = (
<BreadcrumbItem itemType="menu">
<DropdownMenu
items={hiddenItems.map((el, index) => {
return {
...el.props,
text: el.props.children,
disabled: props.disabled,
items: [],
action: (event) => {
if (typeof props.onAction === 'function') {
props.onAction(el.key ?? index);
}

// TODO: move this logic to DropdownMenu
const target = event.currentTarget;
if (
typeof navigate === 'function' &&
target instanceof HTMLAnchorElement
) {
if (el.props.href && shouldClientNavigate(target, event)) {
event.preventDefault();
navigate(el.props.href, el.props.routerOptions);
}
}
},
};
})}
popupProps={{
className: b('popup', {
staircase: props.popupStyle === 'staircase',
}),
placement: props.popupPlacement,
}}
renderSwitcher={({onClick}) => (
<Button
title={i18n('label_more')}
className={b('more-button')}
onClick={onClick}
size="s"
view="flat"
disabled={props.disabled}
>
<Button.Icon>...</Button.Icon>
</Button>
)}
/>
</BreadcrumbItem>
<BreadcrumbsDropdownMenu
disabled={props.disabled}
popupPlacement={props.popupPlacement}
popupStyle={props.popupStyle}
data-breadcrumbs-menu-item={true}
>
{hiddenItems.map((child, index) => {
const Component = props.itemComponent ?? BreadcrumbsItem;
const key = child.key ?? index;
const handleAction = () => {
if (typeof props.onAction === 'function') {
props.onAction(key);
}
};
const innerProps: BreadcrumbsItemInnerProps = {
__index: index,
__disabled: props.disabled || child.props.disabled,
__onAction: handleAction,
};
return (
<Component {...child.props} key={key} {...innerProps}>
{child.props.children}
</Component>
);
})}
</BreadcrumbsDropdownMenu>
);

contents.push(menuItem);
contents.push(...breadcrumbs.slice(-endItems));
}

const lastIndex = contents.length - 1;
const breadcrumbItems = contents.map((child, index) => {
const breadcrumbsItems = contents.map((child, index) => {
const isCurrent = index === lastIndex;
const key = child.key ?? index;
const handleAction = () => {
Expand All @@ -233,18 +190,26 @@ export const Breadcrumbs = React.forwardRef(function Breadcrumbs(
}
};

const {'data-breadcrumbs-menu-item': isMenu, ...childProps} = child.props;
let item: React.ReactNode;
if (isMenu) {
item = child;
} else {
const Component = props.itemComponent ?? BreadcrumbsItem;
const innerProps: BreadcrumbsItemInnerProps = {
__current: isCurrent,
__disabled: props.disabled || childProps.disabled,
__onAction: handleAction,
};
item = (
<Component {...childProps} key={key} {...innerProps}>
{childProps.children}
</Component>
);
}
return (
<li key={index} className={b('item', {calculating: !calculated})}>
<BreadcrumbItem
{...child.props}
key={key}
current={isCurrent}
disabled={props.disabled || child.props.disabled}
onAction={handleAction}
navigate={navigate}
>
{child.props.children}
</BreadcrumbItem>
{item}
{isCurrent ? null : <BreadcrumbsSeparator separator={props.separator} />}
</li>
);
Expand All @@ -257,18 +222,16 @@ export const Breadcrumbs = React.forwardRef(function Breadcrumbs(
className={b(null, props.className)}
style={props.style}
>
{breadcrumbItems}
{breadcrumbsItems}
</ol>
);
}) as unknown as BreadcrumbsComponent;

type BreadcrumbsComponent = React.FunctionComponent<
BreadcrumbsProps & {ref?: React.Ref<HTMLElement>}
> & {
Item: typeof Item;
Item: typeof BreadcrumbsItem;
};

Breadcrumbs.Item = Item;
Breadcrumbs.Item = BreadcrumbsItem;
Breadcrumbs.displayName = 'Breadcrumbs';

export {Item as BreadcrumbsItem};
Loading

0 comments on commit 9a6966e

Please sign in to comment.