Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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`, `off` | `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 @@ -6,17 +6,7 @@

/// <reference types="react" />

import { ButtonProps } from '@fluentui/react-button';
import { ButtonSlots } from '@fluentui/react-button';
import { ButtonState } from '@fluentui/react-button';
import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import { Link } from '@fluentui/react-link';
import { LinkProps } from '@fluentui/react-link';
import * as React_2 from 'react';
import type { Slot } from '@fluentui/react-utilities';
import type { SlotClassNames } from '@fluentui/react-utilities';

// @public
export const Breadcrumb: ForwardRefComponent<BreadcrumbProps>;
Expand Down Expand Up @@ -105,7 +95,7 @@ export type BreadcrumbLinkState = ComponentState<BreadcrumbLinkSlots> & Partial<
// @public
export type BreadcrumbProps = ComponentProps<BreadcrumbSlots> & {
appearance?: 'transparent' | 'subtle';
disableFocus?: boolean;
focusMode?: 'arrow' | 'tab' | 'off';
dividerType?: 'chevron' | 'slash';
iconPosition?: 'before' | 'after';
size?: 'small' | 'medium' | 'large';
Expand All @@ -118,7 +108,7 @@ export type BreadcrumbSlots = {
};

// @public
export type BreadcrumbState = ComponentState<BreadcrumbSlots> & Required<Pick<BreadcrumbProps, 'appearance' | 'iconPosition' | 'size' | 'dividerType'>>;
export type BreadcrumbState = ComponentState<BreadcrumbSlots> & Required<Pick<BreadcrumbProps, 'appearance' | 'iconPosition' | 'size' | 'dividerType' | 'focusMode'>>;

// @public (undocumented)
export type PartitionBreadcrumbItems<T> = {
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,215 @@
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('focusMode="off" (default)', () => {
it('should not be focusable', () => {
mountFluent(<BreadcrumbSampleWithButton focusMode="off" />);

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

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

cy.realPress('Tab');

cy.get('#breadcrumb-button-1').should('not.be.focused');
cy.get('#after').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('focusMode="off" (default)', () => {
it('should not be focusable', () => {
mountFluent(<BreadcrumbSampleWithLink focusMode="off" />);

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

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

cy.realPress('Tab');

cy.get('#breadcrumb-link-1').should('not.be.focused');
cy.get('#after').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 @@ -4,7 +4,7 @@ import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utili
* Data shared between breadcrumb components
*/
export type BreadcrumbContextValue = Required<
Pick<BreadcrumbProps, 'appearance' | 'dividerType' | 'iconPosition' | 'size'>
Pick<BreadcrumbProps, 'appearance' | 'dividerType' | 'focusMode' | 'iconPosition' | 'size'>
>;

export type BreadcrumbContextValues = {
Expand Down Expand Up @@ -34,11 +34,21 @@ 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.
*
* `off`
* The Breadcrumb will not focusable.
*
* @default 'tab'
*/
disableFocus?: boolean;
focusMode?: 'arrow' | 'tab' | 'off';

/**
* Controls type of the divider.
Expand Down Expand Up @@ -66,4 +76,4 @@ export type BreadcrumbProps = ComponentProps<BreadcrumbSlots> & {
* State used in rendering Breadcrumb
*/
export type BreadcrumbState = ComponentState<BreadcrumbSlots> &
Required<Pick<BreadcrumbProps, 'appearance' | 'iconPosition' | 'size' | 'dividerType'>>;
Required<Pick<BreadcrumbProps, 'appearance' | 'iconPosition' | 'size' | 'dividerType' | 'focusMode'>>;
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const breadcrumbDefaultValue: BreadcrumbContextValue = {
iconPosition: 'before',
size: 'medium',
dividerType: 'chevron',
focusMode: 'tab',
};

/**
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,11 +37,12 @@ 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' } }),
appearance,
focusMode,
dividerType,
iconPosition,
size,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import * as React from 'react';
import type { BreadcrumbContextValues, BreadcrumbState } from './Breadcrumb.types';

export function useBreadcrumbContextValues_unstable(state: BreadcrumbState): BreadcrumbContextValues {
const { appearance, dividerType, iconPosition, size } = state;
const { appearance, dividerType, focusMode, iconPosition, size } = state;

const breadcrumb = React.useMemo(() => {
return { appearance, dividerType, iconPosition, size };
}, [appearance, dividerType, iconPosition, size]);
return { appearance, dividerType, focusMode, iconPosition, size };
}, [appearance, dividerType, focusMode, iconPosition, size]);

return { breadcrumb };
}
Loading