# Breadcrumb Link

CoreAI BreadcrumbButton adapter for React Router navigation.

## Using the React Router template

Copy all files listed under **Code** into your project together. The
`RouterLinkSlots.tsx` helper is supplied with the template source; it is not an
npm export.

## Default

Source: CoreAI.

CoreAI BreadcrumbButtons that navigate with React Router.

[Download source](/coreai-design-system/source/examples/templates-react-router-breadcrumb-link--default.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { MemoryRouter } from "react-router-dom";
import { Breadcrumb } from "@coreai-ds/react-components";
import { BreadcrumbItem } from "@coreai-ds/react-components";
import * as React from "react";
import type { BreadcrumbButtonProps as CoreAIBreadcrumbButtonProps } from "@coreai-ds/react-components";
import type { LinkProps as RouterLinkProps } from "react-router-dom";
import { useHref } from "react-router-dom";
import { Link as RouterLink } from "react-router-dom";
import { useBreadcrumbButton_unstable } from "@coreai-ds/react-components";
import { useBreadcrumbButtonStyles_unstable } from "@coreai-ds/react-components";
import { renderBreadcrumbButton_unstable } from "@coreai-ds/react-components";
import { BreadcrumbDivider } from "@coreai-ds/react-components";

type BreadcrumbLinkProps = Omit<CoreAIBreadcrumbButtonProps, keyof RouterLinkProps | 'as' | 'href'> & RouterLinkProps;

const routerLinkPropNames = [
    'defaultShouldRevalidate',
    'discover',
    'download',
    'hrefLang',
    'mask',
    'media',
    'ping',
    'prefetch',
    'preventScrollReset',
    'referrerPolicy',
    'relative',
    'rel',
    'reloadDocument',
    'replace',
    'state',
    'target',
    'to',
    'type',
    'viewTransition',
] as const satisfies readonly (keyof RouterLinkProps)[];

type RouterLinkSlotProps = Pick<RouterLinkProps, (typeof routerLinkPropNames)[number]>;

type SlotRootProps = React.ComponentPropsWithoutRef<'a'> & {
    as?: unknown;
    disabled?: boolean;
    value?: unknown;
};

type ComponentProps<T> = Omit<T, keyof RouterLinkSlotProps>;

function splitRouterLinkProps<T extends RouterLinkSlotProps>(props: T): [
    RouterLinkSlotProps,
    ComponentProps<T>
] {
    const routerProps: Record<string, unknown> = {};
    const componentProps: Record<string, unknown> = { ...props };
    for (const name of routerLinkPropNames) {
        routerProps[name] = props[name];
        delete componentProps[name];
    }
    return [routerProps as RouterLinkSlotProps, componentProps as ComponentProps<T>];
}

function useStableRouterLinkProps(props: RouterLinkSlotProps): RouterLinkSlotProps {
    const { defaultShouldRevalidate, discover, download, hrefLang, mask, media, ping, prefetch, preventScrollReset, referrerPolicy, relative, rel, reloadDocument, replace, state, target, to, type, viewTransition, } = props;
    return React.useMemo(() => ({
        defaultShouldRevalidate,
        discover,
        download,
        hrefLang,
        mask,
        media,
        ping,
        prefetch,
        preventScrollReset,
        referrerPolicy,
        relative,
        rel,
        reloadDocument,
        replace,
        state,
        target,
        to,
        type,
        viewTransition,
    }), [
        defaultShouldRevalidate,
        discover,
        download,
        hrefLang,
        mask,
        media,
        ping,
        prefetch,
        preventScrollReset,
        referrerPolicy,
        relative,
        rel,
        reloadDocument,
        replace,
        state,
        target,
        to,
        type,
        viewTransition,
    ]);
}

function useRouterLinkSlot<T extends RouterLinkSlotProps>(props: T): [
    React.ForwardRefExoticComponent<SlotRootProps & React.RefAttributes<HTMLAnchorElement>>,
    ComponentProps<T>
] {
    const [routerProps, componentProps] = splitRouterLinkProps(props);
    const stableRouterProps = useStableRouterLinkProps(routerProps);
    const RouterLinkSlot = React.useMemo(() => React.forwardRef<HTMLAnchorElement, SlotRootProps>(({ as: _as, disabled, href: _href, onClick, tabIndex, type: _type, ...slotProps }, slotRef) => {
        const isDisabled = disabled ||
            slotProps['aria-disabled'] === true ||
            slotProps['aria-disabled'] === 'true';
        const handleClick: React.MouseEventHandler<HTMLAnchorElement> = (event) => {
            if (isDisabled) {
                event.preventDefault();
                event.stopPropagation();
                return;
            }
            onClick?.(event);
        };
        return (<RouterLink {...slotProps} {...stableRouterProps} target={stableRouterProps.target ?? slotProps.target} rel={stableRouterProps.rel ?? slotProps.rel} aria-disabled={isDisabled ? true : slotProps['aria-disabled']} onClick={handleClick} ref={slotRef} tabIndex={tabIndex ?? (isDisabled ? -1 : undefined)}/>);
    }), [stableRouterProps]);
    return [RouterLinkSlot, componentProps];
}

const BreadcrumbLink = React.forwardRef<HTMLAnchorElement, BreadcrumbLinkProps>((props, ref) => {
    const href = useHref(props.to, { relative: props.relative });
    const [RouterLinkSlot, breadcrumbButtonProps] = useRouterLinkSlot(props);
    const breadcrumbProps = {
        ...(breadcrumbButtonProps as CoreAIBreadcrumbButtonProps),
        as: 'a',
        href,
    } as CoreAIBreadcrumbButtonProps;
    const breadcrumbRef = ref as unknown as React.Ref<HTMLButtonElement>;
    const breadcrumbState = useBreadcrumbButton_unstable(breadcrumbProps, breadcrumbRef);
    (breadcrumbState.root as unknown as {
        as?: React.ElementType;
    }).as = RouterLinkSlot;
    useBreadcrumbButtonStyles_unstable(breadcrumbState);
    return renderBreadcrumbButton_unstable(breadcrumbState);
});

export const Default = () => (<MemoryRouter>
      <Breadcrumb aria-label="Project breadcrumb">
        <BreadcrumbItem>
          <BreadcrumbLink to="/projects">Projects</BreadcrumbLink>
        </BreadcrumbItem>
        <BreadcrumbDivider />
        <BreadcrumbItem>
          <BreadcrumbLink current to="/projects/coreai">
            CoreAI
          </BreadcrumbLink>
        </BreadcrumbItem>
      </Breadcrumb>
    </MemoryRouter>);

export default Default;
```

## Implementation

CoreAI 0.12.0; Fluent 9.74.9.

[Setup and dependencies](/coreai-design-system/guides/Package.md).

Copy this template into your app. It is not an npm export. Adapt actions and navigation to your product.

## Template source: BreadcrumbLink.tsx

[Download BreadcrumbLink.tsx](/coreai-design-system/source/templates/react-router-breadcrumb-link/BreadcrumbLink.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import * as React from 'react';
import {
  renderBreadcrumbButton_unstable,
  useBreadcrumbButtonStyles_unstable,
  useBreadcrumbButton_unstable,
  type BreadcrumbButtonProps as CoreAIBreadcrumbButtonProps,
} from '@coreai-ds/react-components';
import { useHref, type LinkProps as RouterLinkProps } from 'react-router-dom';
import { useRouterLinkSlot } from './RouterLinkSlots';

export type BreadcrumbLinkProps = Omit<
  CoreAIBreadcrumbButtonProps,
  keyof RouterLinkProps | 'as' | 'href'
> &
  RouterLinkProps;

export const BreadcrumbLink = React.forwardRef<HTMLAnchorElement, BreadcrumbLinkProps>(
  (props, ref) => {
    const href = useHref(props.to, { relative: props.relative });
    const [RouterLinkSlot, breadcrumbButtonProps] = useRouterLinkSlot(props);
    const breadcrumbProps = {
      ...(breadcrumbButtonProps as CoreAIBreadcrumbButtonProps),
      as: 'a',
      href,
    } as CoreAIBreadcrumbButtonProps;
    const breadcrumbRef = ref as unknown as React.Ref<HTMLButtonElement>;
    const breadcrumbState = useBreadcrumbButton_unstable(breadcrumbProps, breadcrumbRef);

    (breadcrumbState.root as unknown as { as?: React.ElementType }).as = RouterLinkSlot;
    useBreadcrumbButtonStyles_unstable(breadcrumbState);

    return renderBreadcrumbButton_unstable(breadcrumbState);
  },
);

BreadcrumbLink.displayName = 'BreadcrumbLink';
```

## Template source: RouterLinkSlots.tsx

[Download RouterLinkSlots.tsx](/coreai-design-system/source/templates/react-router-breadcrumb-link/RouterLinkSlots.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import * as React from 'react';
import {
  Link as RouterLink,
  NavLink as RouterNavLink,
  type LinkProps as RouterLinkProps,
  type NavLinkProps as RouterNavLinkProps,
} from 'react-router-dom';

const routerLinkPropNames = [
  'defaultShouldRevalidate',
  'discover',
  'download',
  'hrefLang',
  'mask',
  'media',
  'ping',
  'prefetch',
  'preventScrollReset',
  'referrerPolicy',
  'relative',
  'rel',
  'reloadDocument',
  'replace',
  'state',
  'target',
  'to',
  'type',
  'viewTransition',
] as const satisfies readonly (keyof RouterLinkProps)[];

type RouterLinkSlotProps = Pick<RouterLinkProps, (typeof routerLinkPropNames)[number]>;
type ComponentProps<T> = Omit<T, keyof RouterLinkSlotProps>;
type SlotRootProps = React.ComponentPropsWithoutRef<'a'> & {
  as?: unknown;
  disabled?: boolean;
  value?: unknown;
};

function splitRouterLinkProps<T extends RouterLinkSlotProps>(
  props: T,
): [RouterLinkSlotProps, ComponentProps<T>] {
  const routerProps: Record<string, unknown> = {};
  const componentProps: Record<string, unknown> = { ...props };

  for (const name of routerLinkPropNames) {
    routerProps[name] = props[name];
    delete componentProps[name];
  }

  return [routerProps as RouterLinkSlotProps, componentProps as ComponentProps<T>];
}

function useStableRouterLinkProps(props: RouterLinkSlotProps): RouterLinkSlotProps {
  const {
    defaultShouldRevalidate,
    discover,
    download,
    hrefLang,
    mask,
    media,
    ping,
    prefetch,
    preventScrollReset,
    referrerPolicy,
    relative,
    rel,
    reloadDocument,
    replace,
    state,
    target,
    to,
    type,
    viewTransition,
  } = props;

  return React.useMemo(
    () => ({
      defaultShouldRevalidate,
      discover,
      download,
      hrefLang,
      mask,
      media,
      ping,
      prefetch,
      preventScrollReset,
      referrerPolicy,
      relative,
      rel,
      reloadDocument,
      replace,
      state,
      target,
      to,
      type,
      viewTransition,
    }),
    [
      defaultShouldRevalidate,
      discover,
      download,
      hrefLang,
      mask,
      media,
      ping,
      prefetch,
      preventScrollReset,
      referrerPolicy,
      relative,
      rel,
      reloadDocument,
      replace,
      state,
      target,
      to,
      type,
      viewTransition,
    ],
  );
}

export function useRouterLinkSlot<T extends RouterLinkSlotProps>(
  props: T,
): [
  React.ForwardRefExoticComponent<SlotRootProps & React.RefAttributes<HTMLAnchorElement>>,
  ComponentProps<T>,
] {
  const [routerProps, componentProps] = splitRouterLinkProps(props);
  const stableRouterProps = useStableRouterLinkProps(routerProps);
  const RouterLinkSlot = React.useMemo(
    () =>
      React.forwardRef<HTMLAnchorElement, SlotRootProps>(
        (
          { as: _as, disabled, href: _href, onClick, tabIndex, type: _type, ...slotProps },
          slotRef,
        ) => {
          const isDisabled =
            disabled ||
            slotProps['aria-disabled'] === true ||
            slotProps['aria-disabled'] === 'true';
          const handleClick: React.MouseEventHandler<HTMLAnchorElement> = (event) => {
            if (isDisabled) {
              event.preventDefault();
              event.stopPropagation();
              return;
            }

            onClick?.(event);
          };

          return (
            <RouterLink
              {...slotProps}
              {...stableRouterProps}
              target={stableRouterProps.target ?? slotProps.target}
              rel={stableRouterProps.rel ?? slotProps.rel}
              aria-disabled={isDisabled ? true : slotProps['aria-disabled']}
              onClick={handleClick}
              ref={slotRef}
              tabIndex={tabIndex ?? (isDisabled ? -1 : undefined)}
            />
          );
        },
      ),
    [stableRouterProps],
  );

  return [RouterLinkSlot, componentProps];
}

type RouterNavLinkSlotProps = RouterLinkSlotProps &
  Pick<RouterNavLinkProps, 'caseSensitive' | 'end'>;

export function useRouterNavLinkSlot<T extends RouterNavLinkSlotProps>(
  props: T,
  mode: 'anchor' | 'tab' = 'anchor',
): [
  React.ForwardRefExoticComponent<SlotRootProps & React.RefAttributes<HTMLAnchorElement>>,
  Omit<ComponentProps<T>, 'caseSensitive' | 'end'>,
] {
  const { caseSensitive, end, ...linkProps } = props;
  const [routerProps, componentProps] = splitRouterLinkProps(linkProps as T);
  const stableRouterProps = useStableRouterLinkProps(routerProps);
  const RouterNavLinkSlot = React.useMemo(
    () =>
      React.forwardRef<HTMLAnchorElement, SlotRootProps>(
        (
          {
            as: _as,
            disabled,
            href: _href,
            onClick,
            tabIndex,
            type: _type,
            value: _value,
            ...slotProps
          },
          slotRef,
        ) => {
          const handleClick: React.MouseEventHandler<HTMLAnchorElement> = (event) => {
            if (mode === 'tab' && disabled) {
              event.preventDefault();
              event.stopPropagation();
              return;
            }

            onClick?.(event);
          };

          return (
            <RouterNavLink
              {...slotProps}
              {...stableRouterProps}
              target={stableRouterProps.target ?? slotProps.target}
              rel={stableRouterProps.rel ?? slotProps.rel}
              aria-disabled={mode === 'tab' && disabled ? true : undefined}
              caseSensitive={caseSensitive}
              end={end}
              onClick={handleClick}
              ref={slotRef}
              tabIndex={mode === 'tab' && disabled ? -1 : tabIndex}
            />
          );
        },
      ),
    [caseSensitive, end, mode, stableRouterProps],
  );

  return [RouterNavLinkSlot, componentProps];
}
```