Skip to content

Commit

Permalink
fix: simplify router integration, fix styles
Browse files Browse the repository at this point in the history
  • Loading branch information
ValeraS committed Jan 24, 2025
1 parent cd70dae commit 3de10ed
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 323 deletions.
90 changes: 41 additions & 49 deletions src/components/Breadcrumbs/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,22 @@ import type {AriaLabelingProps, DOMProps, Key, QAProps} from '../types';
import {filterDOMProps} from '../utils/filterDOMProps';

import {BreadcrumbsDropdownMenu} from './BreadcrumbsDropdownMenu';
import {BreadcrumbsItemView} from './BreadcrumbsItemView';
import {BreadcrumbsItem} from './BreadcrumbsItem';
import type {BreadcrumbsItemInnerProps} from './BreadcrumbsItem';
import {BreadcrumbsSeparator} from './BreadcrumbsSeparator';
import {b} from './utils';

import './Breadcrumbs.scss';

export type BreadcrumbsItemProps<T extends React.ElementType> =
React.ComponentPropsWithoutRef<T> & {
component?: T;
disabled?: boolean;
};

function Item<T extends React.ElementType = 'a'>(
_props: BreadcrumbsItemProps<T>,
): React.ReactElement | null {
return null;
}

export interface BreadcrumbsProps<T extends React.ElementType = 'a'>
extends DOMProps,
AriaLabelingProps,
QAProps {
export interface BreadcrumbsProps extends DOMProps, AriaLabelingProps, QAProps {
id?: string;
showRoot?: boolean;
separator?: React.ReactNode;
maxItems?: number;
popupStyle?: 'staircase';
popupPlacement?: PopupPlacement;
children:
| React.ReactElement<BreadcrumbsItemProps<T>>
| React.ReactElement<BreadcrumbsItemProps<T>>[];
itemComponent?: React.ElementType;
children: React.ReactNode;
disabled?: boolean;
onAction?: (key: Key) => void;
}
Expand Down Expand Up @@ -167,22 +152,23 @@ export const Breadcrumbs = React.forwardRef(function Breadcrumbs(
disabled={props.disabled}
popupPlacement={props.popupPlacement}
popupStyle={props.popupStyle}
component={BreadcrumbsDropdownMenu}
data-breadcrumbs-menu-item={true}
>
{hiddenItems.map((child, index) => {
const Component = child.props.component ?? BreadcrumbsItemView;
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}
index={index}
key={child.key}
disabled={props.disabled || child.props.disabled}
onAction={() => {
if (typeof props.onAction === 'function') {
props.onAction(child.key ?? index);
}
}}
>
<Component {...child.props} key={key} {...innerProps}>
{child.props.children}
</Component>
);
Expand All @@ -195,7 +181,7 @@ export const Breadcrumbs = React.forwardRef(function Breadcrumbs(
}

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 @@ -204,18 +190,26 @@ export const Breadcrumbs = React.forwardRef(function Breadcrumbs(
}
};

const {component: Component = BreadcrumbsItemView, ...childProps} = child.props;
return (
<li key={index} className={b('item', {calculating: !calculated})}>
<Component
{...childProps}
key={key}
current={isCurrent}
disabled={props.disabled || childProps.disabled}
onAction={handleAction}
>
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})}>
{item}
{isCurrent ? null : <BreadcrumbsSeparator separator={props.separator} />}
</li>
);
Expand All @@ -228,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};
1 change: 0 additions & 1 deletion src/components/Breadcrumbs/BreadcrumbsDropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ interface DropdownMenuProps {
disabled?: boolean;
popupPlacement?: PopupPlacement;
popupStyle?: 'staircase';
component?: typeof BreadcrumbsDropdownMenu;
}

interface MenuContext {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,22 @@ import {filterDOMProps} from '../utils/filterDOMProps';
import {useMenuContext} from './BreadcrumbsDropdownMenu';
import {b} from './utils';

export interface BreadcrumbsItemViewProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
children: React.ReactNode;
export interface BreadcrumbsItemInnerProps {
__disabled?: boolean;
__onAction?: () => void;
__current?: boolean;
__index?: number;
}

export interface BreadcrumbsItemProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
disabled?: boolean;
onAction?: () => void;
current?: boolean;
index?: number;
children?: React.ReactNode;
}

function BreadcrumbsItem(
props: BreadcrumbsItemViewProps,
ref: React.ForwardedRef<HTMLAnchorElement>,
) {
function BreadcrumbsItem(props: BreadcrumbsItemProps, ref: React.ForwardedRef<HTMLAnchorElement>) {
const domProps = filterDOMProps(props, {labelable: true});

const {
disabled,
current,
href,
hrefLang,
target,
Expand All @@ -33,35 +32,37 @@ function BreadcrumbsItem(
ping,
referrerPolicy,
children,
onAction,
index,
__disabled: disabled,
__current: current,
__onAction: onAction,
__index: index,
...restProps
} = props;
} = props as BreadcrumbsItemProps & BreadcrumbsItemInnerProps;

let title = props.title;
if (!title && typeof children === 'string') {
title = children;
}

const handleAction = (event: React.MouseEvent<HTMLElement>) => {
const handleAction = (event: React.MouseEvent<HTMLAnchorElement>) => {
if (disabled) {
event.preventDefault();
return;
}

if (typeof restProps.onClick === 'function') {
restProps.onClick(event as any);
restProps.onClick(event);
}

if (typeof onAction === 'function') {
onAction();
}
};

const isDisabled = props.disabled;
const linkProps: React.AnchorHTMLAttributes<HTMLElement> = {
const linkProps: React.AnchorHTMLAttributes<HTMLAnchorElement> = {
title,
onClick: handleAction,
'aria-disabled': isDisabled ? true : undefined,
'aria-disabled': disabled ? true : undefined,
};

if (href) {
Expand All @@ -72,18 +73,18 @@ function BreadcrumbsItem(
linkProps.download = download;
linkProps.ping = ping;
linkProps.referrerPolicy = referrerPolicy;
linkProps.tabIndex = isDisabled ? -1 : undefined;
linkProps.tabIndex = disabled ? -1 : undefined;
} else {
linkProps.role = 'link';
linkProps.tabIndex = isDisabled ? undefined : 0;
linkProps.tabIndex = disabled ? undefined : 0;
linkProps.onKeyDown = (event) => {
if (disabled) {
event.preventDefault();
return;
}

if (typeof restProps.onKeyDown === 'function') {
restProps.onKeyDown(event as any);
restProps.onKeyDown(event);
}

if (event.key === 'Enter') {
Expand All @@ -102,7 +103,7 @@ function BreadcrumbsItem(

const {isMenu, getItemProps, listItemsRef, activeIndex, popupStyle} = useMenuContext();
if (isMenu) {
const active = !isDisabled && activeIndex === index;
const active = !disabled && activeIndex === index;
return (
<ListItemView
{...getItemProps({
Expand All @@ -121,7 +122,7 @@ function BreadcrumbsItem(
size="m"
className={b('menu-link', props.className)}
component={Element}
disabled={isDisabled}
disabled={disabled}
>
{children}
</ListItemView>
Expand All @@ -138,7 +139,7 @@ function BreadcrumbsItem(
'link',
{
'is-current': current,
'is-disabled': isDisabled && !current,
'is-disabled': disabled && !current,
},
props.className,
)}
Expand All @@ -150,4 +151,5 @@ function BreadcrumbsItem(

BreadcrumbsItem.displayName = 'Breadcrumbs.Item';

export const BreadcrumbsItemView = React.forwardRef(BreadcrumbsItem);
const _BreadcrumbsItem = React.forwardRef(BreadcrumbsItem);
export {_BreadcrumbsItem as BreadcrumbsItem};
56 changes: 19 additions & 37 deletions src/components/Breadcrumbs/README-ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,32 +336,26 @@ LANDING_BLOCK-->

### Интеграция с роутерами

<!--/GITHUB_BLOCK-->
<!--GITHUB_BLOCK-->

#### React Router

```jsx
import {useLinkClickHandler, useHref} from 'react-router';
import {Breadcrumbs, BreadcrumbsItemView} from '@gravity-ui/uikit';
import {Breadcrumbs, BreadcrumbsItem} from '@gravity-ui/uikit';

function RouterLink({to, ...rest}) {
const href = useHref(to);
const onClick = useLinkClickHandler(to);
return <BreadcrumbsItemView {...rest} href={href} onClick={onClick} />;
return <BreadcrumbsItem {...rest} href={href} onClick={onClick} />;
}

function Navigation() {
return (
<Breadcrumbs>
<Breadcrumbs.Item to="/" component={RouterLink}>
Home
</Breadcrumbs.Item>
<Breadcrumbs.Item to="/components" component={RouterLink}>
Components
</Breadcrumbs.Item>
<Breadcrumbs.Item to="/components/breadcrumbs" component={RouterLink}>
Breadcrumbs
</Breadcrumbs.Item>
<Breadcrumbs itemComponent={RouterLink}>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/components">Components</RouterLink>
<RouterLink to="/components/breadcrumbs">Breadcrumbs</RouterLink>
</Breadcrumbs>
);
}
Expand All @@ -371,28 +365,22 @@ function Navigation() {

```jsx
import Link from 'next/link';
import {Breadcrumbs, BreadcrumbsItemView} from '@gravity-ui/uikit';
import {Breadcrumbs, BreadcrumbsItem} from '@gravity-ui/uikit';

function RouterLink({href, ...rest}) {
return (
<Link href={href} passHref legacyBehavior>
<BreadcrumbsItemView {...rest} />;
<BreadcrumbsItem {...rest} />;
</Link>
);
}

function Navigation() {
return (
<Breadcrumbs>
<Breadcrumbs.Item href="/" component={RouterLink}>
Home
</Breadcrumbs.Item>
<Breadcrumbs.Item href="/components" component={RouterLink}>
Components
</Breadcrumbs.Item>
<Breadcrumbs.Item href="/components/breadcrumbs" component={RouterLink}>
Breadcrumbs
</Breadcrumbs.Item>
<Breadcrumbs itemComponent={RouterLink}>
<RouterLink href="/">Home</RouterLink>
<RouterLink href="/components">Components</RouterLink>
<RouterLink href="/components/breadcrumbs">Breadcrumbs</RouterLink>
</Breadcrumbs>
);
}
Expand All @@ -402,22 +390,16 @@ function Navigation() {

```jsx
import {createLink} from '@tanstack/react-router';
import {Breadcrumbs, BreadcrumbsItemView} from '@gravity-ui/uikit';
import {Breadcrumbs, BreadcrumbsItem} from '@gravity-ui/uikit';

const RouterLink = createLink(BreadcrumbsItemView);
const RouterLink = createLink(BreadcrumbsItem);

function Navigation() {
return (
<Breadcrumbs>
<Breadcrumbs.Item href="/" component={RouterLink}>
Home
</Breadcrumbs.Item>
<Breadcrumbs.Item href="/components" component={RouterLink}>
Components
</Breadcrumbs.Item>
<Breadcrumbs.Item href="/components/breadcrumbs" component={RouterLink}>
Breadcrumbs
</Breadcrumbs.Item>
<Breadcrumbs itemComponent={RouterLink}>
<RouterLink href="/">Home</RouterLink>
<RouterLink href="/components">Components</RouterLink>
<RouterLink href="/components/breadcrumbs">Breadcrumbs</RouterLink>
</Breadcrumbs>
);
}
Expand Down
Loading

0 comments on commit 3de10ed

Please sign in to comment.