Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { baseConfig } from '@fluentui/scripts-cypress';

export default baseConfig;
1 change: 1 addition & 0 deletions packages/react-components/react-breadcrumb/docs/Spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ Dropdown contains collapsed items.
| ------------ | -------------------------- | ------------- | -------------------------------- |
| appearance | `transparent`, `subtle` | `transparent` | Sets appearance |
| dividerType | `chevron`, `slash` | `chevron` | Sets type of divider |
| focusMode | `tab`, `arrow` | `tab` | Sets focus mode |
| iconPosition | `before`, `after` | `before` | Sets icon position for all items |
| size | `small`, `medium`, `large` | `medium` | Defines size of the Breadcrumb |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export type BreadcrumbLinkState = ComponentState<BreadcrumbLinkSlots> & Partial<
// @public
export type BreadcrumbProps = ComponentProps<BreadcrumbSlots> & {
appearance?: 'transparent' | 'subtle';
disableFocus?: boolean;
focusMode?: 'arrow' | 'tab';
dividerType?: 'chevron' | 'slash';
iconPosition?: 'before' | 'after';
size?: 'small' | 'medium' | 'large';
Expand Down
3 changes: 3 additions & 0 deletions packages/react-components/react-breadcrumb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"build": "just-scripts build",
"clean": "just-scripts clean",
"code-style": "just-scripts code-style",
"e2e": "cypress run --component",
"e2e:local": "cypress open --component",
"just": "just-scripts",
"lint": "just-scripts lint",
"test": "jest --passWithNoTests",
Expand All @@ -30,6 +32,7 @@
"@fluentui/react-conformance": "*",
"@fluentui/react-conformance-griffel": "9.0.0-beta.22",
"@fluentui/scripts-api-extractor": "*",
"@fluentui/scripts-cypress": "*",
"@fluentui/scripts-tasks": "*"
},
"dependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import * as React from 'react';
import { mount } from '@cypress/react';
import type {} from '@cypress/react';
import { FluentProvider } from '@fluentui/react-provider';
import { webLightTheme } from '@fluentui/react-theme';
import { Breadcrumb, BreadcrumbItem, BreadcrumbButton, BreadcrumbLink } from '@fluentui/react-breadcrumb';
import type { BreadcrumbProps } from '@fluentui/react-breadcrumb';

const mountFluent = (element: JSX.Element) => {
mount(<FluentProvider theme={webLightTheme}>{element}</FluentProvider>);
};

const BreadcrumbSampleWithButton = (props: BreadcrumbProps) => (
<>
<p tabIndex={0} id="before">
Before
</p>

<Breadcrumb {...props} id="breadcrumb">
<BreadcrumbItem>
<BreadcrumbButton id="breadcrumb-button-1">Item 1</BreadcrumbButton>
</BreadcrumbItem>
<BreadcrumbItem>
<BreadcrumbButton id="breadcrumb-button-2">Item 2</BreadcrumbButton>
</BreadcrumbItem>
<BreadcrumbItem>
<BreadcrumbButton id="breadcrumb-button-3" current>
Item 3
</BreadcrumbButton>
</BreadcrumbItem>
</Breadcrumb>

<p tabIndex={0} id="after">
After
</p>
</>
);

const BreadcrumbSampleWithLink = (props: BreadcrumbProps) => (
<>
<p tabIndex={0} id="before">
Before
</p>

<Breadcrumb {...props} id="breadcrumb">
<BreadcrumbItem>
<BreadcrumbLink href="#" id="breadcrumb-link-1">
Item 1
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbItem>
<BreadcrumbLink href="#" id="breadcrumb-link-2">
Item 2
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbItem>
<BreadcrumbLink href="#" id="breadcrumb-link-3" current>
Item 3
</BreadcrumbLink>
</BreadcrumbItem>
</Breadcrumb>

<p tabIndex={0} id="after">
After
</p>
</>
);

const NonInteractiveBreadcrumbSample = (props: BreadcrumbProps) => (
<>
<p tabIndex={0} id="before">
Before
</p>

<Breadcrumb {...props} id="breadcrumb">
<BreadcrumbItem id="breadcrumb-item-1">Item 1</BreadcrumbItem>
<BreadcrumbItem id="breadcrumb-item-2">Item 2</BreadcrumbItem>
<BreadcrumbItem id="breadcrumb-item-3" current>
Item 3
</BreadcrumbItem>
</Breadcrumb>

<p tabIndex={0} id="after">
After
</p>
</>
);

describe('Breadcrumb', () => {
describe('focus behaviors for BreadcrumbButton', () => {
describe('focusMode="tab"(default)', () => {
it('should be focusable', () => {
mountFluent(<BreadcrumbSampleWithButton />);

cy.get('#before').focus();

cy.get('#breadcrumb-button-1').should('not.be.focused');

cy.realPress('Tab');

cy.get('#breadcrumb-button-1').should('be.focused');
cy.realPress('Tab');
cy.get('#breadcrumb-button-2').should('be.focused');
cy.realPress('Tab');
cy.get('#breadcrumb-button-3').should('be.focused');
});
});

describe('focusMode="arrow"', () => {
it('should be focusable', () => {
mountFluent(<BreadcrumbSampleWithButton focusMode="arrow" />);

cy.get('#before').focus();

cy.get('#breadcrumb-button-1').should('not.be.focused');

cy.realPress('Tab');

cy.get('#breadcrumb-button-1').should('be.focused');
cy.realPress('ArrowRight');
cy.get('#breadcrumb-button-2').should('be.focused');
cy.realPress('ArrowRight');
cy.get('#breadcrumb-button-3').should('be.focused');
cy.realPress('ArrowRight');
cy.get('#breadcrumb-button-1').should('be.focused');
});
});
});
describe('focus behaviors for BreadcrumbLink', () => {
describe('focusMode="tab"(default)', () => {
it('should be focusable', () => {
mountFluent(<BreadcrumbSampleWithLink />);

cy.get('#before').focus();

cy.get('#breadcrumb-link-1').should('not.be.focused');

cy.realPress('Tab');

cy.get('#breadcrumb-link-1').should('be.focused');
cy.realPress('Tab');
cy.get('#breadcrumb-link-2').should('be.focused');
cy.realPress('Tab');
cy.get('#breadcrumb-link-3').should('be.focused');
});
});

describe('focusMode="arrow"', () => {
it('should be focusable', () => {
mountFluent(<BreadcrumbSampleWithLink focusMode="arrow" />);

cy.get('#before').focus();

cy.get('#breadcrumb-link-1').should('not.be.focused');

cy.realPress('Tab');

cy.get('#breadcrumb-link-1').should('be.focused');
cy.realPress('ArrowRight');
cy.get('#breadcrumb-link-2').should('be.focused');
cy.realPress('ArrowRight');
cy.get('#breadcrumb-link-3').should('be.focused');
cy.realPress('ArrowRight');
cy.get('#breadcrumb-link-1').should('be.focused');
});
});
});
describe('focus behaviors for BreadcrumbItem', () => {
it('should not be focusable', () => {
mountFluent(<NonInteractiveBreadcrumbSample />);

cy.get('#before').focus();

cy.get('#breadcrumb-item-1').should('not.be.focused');
cy.get('#before').should('be.focused');

cy.realPress('Tab');

cy.get('#breadcrumb-item-1').should('not.be.focused');
cy.get('#after').should('be.focused');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ describe('Breadcrumb', () => {
<nav
aria-label="breadcrumb"
class="fui-Breadcrumb"
data-tabster="{\\"mover\\":{\\"cyclic\\":true,\\"direction\\":2,\\"memorizeCurrent\\":true}}"
>
<ol
class="fui-Breadcrumb__list"
Expand Down Expand Up @@ -62,7 +61,6 @@ describe('Breadcrumb', () => {
<nav
aria-label="breadcrumb"
class="fui-Breadcrumb"
data-tabster="{\\"mover\\":{\\"cyclic\\":true,\\"direction\\":2,\\"memorizeCurrent\\":true}}"
>
<ol
class="fui-Breadcrumb__list"
Expand Down Expand Up @@ -102,7 +100,6 @@ describe('Breadcrumb', () => {
<nav
aria-label="breadcrumb"
class="fui-Breadcrumb"
data-tabster="{\\"mover\\":{\\"cyclic\\":true,\\"direction\\":2,\\"memorizeCurrent\\":true}}"
>
<ol
class="fui-Breadcrumb__list"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,18 @@ export type BreadcrumbProps = ComponentProps<BreadcrumbSlots> & {
appearance?: 'transparent' | 'subtle';

/**
* Makes Breadcrumb not focusable.
* Sets the focus behavior for the Breadcrumb.
*
* @default false
* `tab`
* This behaviour will cycle through all elements inside of the Breadcrumb when pressing the Tab key and then release focus
* after the last inner element.
*
* `arrow`
* This behaviour will cycle through all elements inside of the Breadcrumb when pressing the Arrow key.
*
* @default 'tab'
*/
disableFocus?: boolean;
focusMode?: 'arrow' | 'tab';

/**
* Controls type of the divider.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useArrowNavigationGroup } from '@fluentui/react-tabster';
export const useBreadcrumb_unstable = (props: BreadcrumbProps, ref: React.Ref<HTMLElement>): BreadcrumbState => {
const {
appearance = 'transparent',
disableFocus,
focusMode = 'tab',
dividerType = 'chevron',
iconPosition = 'before',
size = 'medium',
Expand All @@ -37,7 +37,7 @@ export const useBreadcrumb_unstable = (props: BreadcrumbProps, ref: React.Ref<HT
root: getNativeElementProps('nav', {
ref,
'aria-label': props['aria-label'] ?? 'breadcrumb',
...(!disableFocus ? focusAttributes : {}),
...(focusMode === 'arrow' ? focusAttributes : {}),
...rest,
}),
list: resolveShorthand(list, { required: true, defaultProps: { role: 'list' } }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ describe('BreadcrumbDivider', () => {
<nav
aria-label="breadcrumb"
class="fui-Breadcrumb"
data-tabster="{\\"mover\\":{\\"cyclic\\":true,\\"direction\\":2,\\"memorizeCurrent\\":true}}"
>
<ol
class="fui-Breadcrumb__list"
Expand Down Expand Up @@ -82,7 +81,6 @@ describe('BreadcrumbDivider', () => {
<nav
aria-label="breadcrumb"
class="fui-Breadcrumb"
data-tabster="{\\"mover\\":{\\"cyclic\\":true,\\"direction\\":2,\\"memorizeCurrent\\":true}}"
>
<ol
class="fui-Breadcrumb__list"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import * as React from 'react';
import { Breadcrumb, BreadcrumbItem, BreadcrumbButton, BreadcrumbDivider } from '@fluentui/react-breadcrumb';
import { bundleIcon, CalendarMonth20Filled, CalendarMonth20Regular, GridDots20Regular } from '@fluentui/react-icons';
import { ButtonProps } from '@fluentui/react-button';

const CalendarMonth = bundleIcon(CalendarMonth20Filled, CalendarMonth20Regular);

type Item = {
key: number;
item?: string;
buttonProps?: {
'aria-label'?: string;
onClick?: () => void;
icon?: ButtonProps['icon'];
disabled?: boolean;
iconPosition?: 'before' | 'after';
};
};

const buttonItems: Item[] = [
{
key: 0,
item: 'Item 0',
},
{
key: 1,
buttonProps: {
icon: <CalendarMonth />,
'aria-label': 'Item 1',
},
},
{
key: 2,
item: 'Item 2',
buttonProps: {
icon: <GridDots20Regular />,
},
},
{
key: 3,
item: 'Item 3',
},
{
key: 4,
item: 'Item 4',
buttonProps: {
icon: <CalendarMonth />,
iconPosition: 'after',
},
},
{
key: 5,
item: 'Item 5',
},
];

function renderButton(el: Item, isLastItem: boolean = false) {
return (
<React.Fragment key={`${el.key}-button`}>
<BreadcrumbItem>
<BreadcrumbButton
{...el.buttonProps}
current={isLastItem}
onClick={isLastItem ? undefined : el.buttonProps?.onClick}
>
{el.item}
</BreadcrumbButton>
</BreadcrumbItem>
{!isLastItem && <BreadcrumbDivider />}
</React.Fragment>
);
}
export const BreadcrumbFocusMode = () => (
<>
<h2>`tab` - default</h2>
Navigation using `tab` key.
<Breadcrumb aria-label="Breadcrumb with `tab` focusMode">
{buttonItems.map(el => renderButton(el, el.key === buttonItems.length - 1))}
</Breadcrumb>
<h2>`arrow`</h2>
Navigation using arrow keys
<Breadcrumb aria-label="Breadcrumb with `arrow` focusMode" focusMode="arrow">
{buttonItems.map(el => renderButton(el, el.key === buttonItems.length - 1))}
</Breadcrumb>
</>
);
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import bestPracticesMd from './BreadcrumbBestPractices.md';

export { Default } from './BreadcrumbDefault.stories';
export { BreadcrumbWithOverflow } from './BreadcrumbWithOverflow.stories';
export { BreadcrumbFocusMode } from './BreadcrumbFocusMode';

export default {
title: 'Preview Components/Breadcrumb',
Expand Down
Loading