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
Expand Up @@ -12,6 +12,7 @@ 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 * as React_2 from 'react';
import type { Slot } from '@fluentui/react-utilities';
import type { SlotClassNames } from '@fluentui/react-utilities';
Expand Down Expand Up @@ -75,6 +76,31 @@ export type BreadcrumbItemSlots = {
// @public
export type BreadcrumbItemState = ComponentState<BreadcrumbItemSlots> & Required<Pick<BreadcrumbItemProps, 'size' | 'current'>>;

// @public
export const BreadcrumbLink: ForwardRefComponent<BreadcrumbLinkProps>;

// @public (undocumented)
export const breadcrumbLinkClassNames: SlotClassNames<BreadcrumbLinkSlots>;

// @public
export type BreadcrumbLinkProps = ComponentProps<BreadcrumbLinkSlots> & {
current?: boolean;
iconPosition?: 'before' | 'after';
overflow?: boolean;
size?: 'small' | 'medium' | 'large';
};

// @public (undocumented)
export type BreadcrumbLinkSlots = {
root: Slot<typeof Link>;
icon?: Slot<'span'>;
};

// @public
export type BreadcrumbLinkState = ComponentState<BreadcrumbLinkSlots> & Required<Pick<BreadcrumbLinkProps, 'iconPosition' | 'disabled' | 'overflow' | 'current' | 'size'>> & {
iconOnly: boolean;
};

// @public
export type BreadcrumbProps = ComponentProps<BreadcrumbSlots> & {
appearance?: 'transparent' | 'subtle';
Expand Down Expand Up @@ -104,6 +130,9 @@ export const renderBreadcrumbDivider_unstable: (state: BreadcrumbDividerState) =
// @public
export const renderBreadcrumbItem_unstable: (state: BreadcrumbItemState) => JSX.Element;

// @public
export const renderBreadcrumbLink_unstable: (state: BreadcrumbLinkState) => JSX.Element;

// @public
export const useBreadcrumb_unstable: (props: BreadcrumbProps, ref: React_2.Ref<HTMLElement>) => BreadcrumbState;

Expand All @@ -125,6 +154,12 @@ export const useBreadcrumbItem_unstable: (props: BreadcrumbItemProps, ref: React
// @public
export const useBreadcrumbItemStyles_unstable: (state: BreadcrumbItemState) => BreadcrumbItemState;

// @public
export const useBreadcrumbLink_unstable: (props: BreadcrumbLinkProps, ref: React_2.Ref<HTMLElement>) => BreadcrumbLinkState;

// @public
export const useBreadcrumbLinkStyles_unstable: (state: BreadcrumbLinkState) => BreadcrumbLinkState;

// @public
export const useBreadcrumbStyles_unstable: (state: BreadcrumbState) => BreadcrumbState;

Expand Down
1 change: 1 addition & 0 deletions packages/react-components/react-breadcrumb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"dependencies": {
"@fluentui/react-button": "^9.3.8",
"@fluentui/react-icons": "^2.0.196",
"@fluentui/react-link": "^9.0.34",
"@fluentui/react-shared-contexts": "^9.3.3",
"@fluentui/react-theme": "^9.1.7",
"@fluentui/react-utilities": "^9.7.4",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/BreadcrumbLink/index';
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import { BreadcrumbLink } from './BreadcrumbLink';
import type { BreadcrumbLinkProps } from './BreadcrumbLink.types';
import { isConformant } from '../../testing/isConformant';
import { breadcrumbLinkClassNames } from './useBreadcrumbLinkStyles';

describe('BreadcrumbLink', () => {
isConformant({
Component: BreadcrumbLink as React.FunctionComponent<BreadcrumbLinkProps>,
displayName: 'BreadcrumbLink',
testOptions: {
'has-static-classnames': [
{
props: {},
expectedClassNames: {
root: breadcrumbLinkClassNames.root,
},
},
],
},
});

// TODO add more tests here, and create visual regression tests in /apps/vr-tests

it('renders a default state', () => {
const result = render(<BreadcrumbLink>Default BreadcrumbLink</BreadcrumbLink>);
expect(result.container).toMatchInlineSnapshot(`
<div>
<button
class="fui-Link fui-BreadcrumbLink"
type="button"
>
Default BreadcrumbLink
</button>
</div>
`);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';
import { useBreadcrumbLink_unstable } from './useBreadcrumbLink';
import { renderBreadcrumbLink_unstable } from './renderBreadcrumbLink';
import { useBreadcrumbLinkStyles_unstable } from './useBreadcrumbLinkStyles';
import type { BreadcrumbLinkProps } from './BreadcrumbLink.types';
import type { ForwardRefComponent } from '@fluentui/react-utilities';

/**
* BreadcrumbLink component - TODO: add more docs
*/
export const BreadcrumbLink: ForwardRefComponent<BreadcrumbLinkProps> = React.forwardRef((props, ref) => {
const state = useBreadcrumbLink_unstable(props, ref);

useBreadcrumbLinkStyles_unstable(state);
return renderBreadcrumbLink_unstable(state);
});

BreadcrumbLink.displayName = 'BreadcrumbLink';
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import { Link } from '@fluentui/react-link';

export type BreadcrumbLinkSlots = {
root: Slot<typeof Link>;
icon?: Slot<'span'>;
};

/**
* BreadcrumbLink Props
*/
export type BreadcrumbLinkProps = ComponentProps<BreadcrumbLinkSlots> & {
/**
* Defines current sate of BreadcrumbLink.
*
* @default false
*/
current?: boolean;

/**
* Icon position for BreadcrumbLink or BreadcrumbLink.
*
* @default 'before'
*/
iconPosition?: 'before' | 'after';

/**
* Defines a sate when the Link is part of overflow menu.
*
* @default false
*/
overflow?: boolean;

/**
* Controls size of Breadcrumb items and dividers.
*
* @default 'medium'
*/
size?: 'small' | 'medium' | 'large';
};

/**
* State used in rendering BreadcrumbLink
*/
export type BreadcrumbLinkState = ComponentState<BreadcrumbLinkSlots> &
Required<Pick<BreadcrumbLinkProps, 'iconPosition' | 'disabled' | 'overflow' | 'current' | 'size'>> & {
iconOnly: boolean;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './BreadcrumbLink';
export * from './BreadcrumbLink.types';
export * from './renderBreadcrumbLink';
export * from './useBreadcrumbLink';
export * from './useBreadcrumbLinkStyles';
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';
import { getSlots } from '@fluentui/react-utilities';
import type { BreadcrumbLinkState, BreadcrumbLinkSlots } from './BreadcrumbLink.types';

/**
* Render the final JSX of BreadcrumbLink
*/
export const renderBreadcrumbLink_unstable = (state: BreadcrumbLinkState) => {
const { slots, slotProps } = getSlots<BreadcrumbLinkSlots>(state);
const { iconOnly, iconPosition } = state;
return (
<slots.root {...slotProps.root}>
{iconPosition !== 'after' && slots.icon && <slots.icon {...slotProps.icon} />}
{!iconOnly && state.root.children}
{iconPosition === 'after' && slots.icon && <slots.icon {...slotProps.icon} />}
</slots.root>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as React from 'react';
import { getNativeElementProps, resolveShorthand } from '@fluentui/react-utilities';
import type { BreadcrumbLinkProps, BreadcrumbLinkState } from './BreadcrumbLink.types';
import { Link } from '@fluentui/react-link';
import { useBreadcrumbContext_unstable } from '../Breadcrumb/BreadcrumbContext';
/**
* Create the state required to render BreadcrumbLink.
*
* The returned state can be modified with hooks such as useBreadcrumbLinkStyles_unstable,
* before being passed to renderBreadcrumbLink_unstable.
*
* @param props - props from this instance of BreadcrumbLink
* @param ref - reference to root HTMLElement of BreadcrumbLink
*/
export const useBreadcrumbLink_unstable = (
props: BreadcrumbLinkProps,
ref: React.Ref<HTMLElement>,
): BreadcrumbLinkState => {
const { iconPosition, size } = useBreadcrumbContext_unstable();
const { current = false, disabled = false, icon, overflow = false, ...rest } = props;

const iconShorthand = resolveShorthand(icon);

return {
components: {
root: Link,
icon: 'span',
},
root: getNativeElementProps('a', {
ref,
...rest,
}),
current,
disabled,
icon: iconShorthand,
iconOnly: Boolean(iconShorthand?.children && !props.children),
iconPosition: props.iconPosition || iconPosition,
overflow,
size: props.size || size,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { makeStyles, mergeClasses, shorthands } from '@griffel/react';
import type { BreadcrumbLinkSlots, BreadcrumbLinkState } from './BreadcrumbLink.types';
import type { SlotClassNames } from '@fluentui/react-utilities';
import { tokens, typographyStyles } from '@fluentui/react-theme';

export const breadcrumbLinkClassNames: SlotClassNames<BreadcrumbLinkSlots> = {
root: 'fui-BreadcrumbLink',
icon: 'fui-BreadcrumbLink__icon',
};

/**
* Styles for the root slot
*/
const useStyles = makeStyles({
root: {
display: 'flex',
},
icon: {
display: 'flex',
alignItems: 'center',
...shorthands.padding(tokens.spacingHorizontalXS),
},
small: {
height: '24px',
...shorthands.padding(tokens.spacingHorizontalSNudge),
...typographyStyles.caption1,
},
medium: {
height: '32px',
...shorthands.padding(tokens.spacingHorizontalSNudge),
...typographyStyles.body1,
},
large: {
height: '40px',
...shorthands.padding(tokens.spacingHorizontalS),
...typographyStyles.body2,
},
overflow: {
...shorthands.padding(tokens.spacingHorizontalSNudge),
'&:hover': {
textDecorationLine: 'none',
},
},
currentSmall: {
...typographyStyles.caption1Strong,
},
currentMedium: {
...typographyStyles.body1Strong,
},
currentLarge: {
...typographyStyles.subtitle2,
},
});

/**
* Apply styling to the BreadcrumbLink slots based on the state
*/
export const useBreadcrumbLinkStyles_unstable = (state: BreadcrumbLinkState): BreadcrumbLinkState => {
const styles = useStyles();

const currentSizeMap = {
small: styles.currentSmall,
medium: styles.currentMedium,
large: styles.currentLarge,
};

state.root.className = mergeClasses(
breadcrumbLinkClassNames.root,
styles.root,
styles[state.size],
state.overflow && styles.overflow,
state.current && currentSizeMap[state.size],
state.root.className,
);

if (state.icon) {
state.icon.className = mergeClasses(styles.icon, state.icon.className);
}

return state;
};
8 changes: 8 additions & 0 deletions packages/react-components/react-breadcrumb/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,11 @@ export {
useBreadcrumbButton_unstable,
} from './BreadcrumbButton';
export type { BreadcrumbButtonProps, BreadcrumbButtonSlots, BreadcrumbButtonState } from './BreadcrumbButton';
export {
BreadcrumbLink,
breadcrumbLinkClassNames,
renderBreadcrumbLink_unstable,
useBreadcrumbLinkStyles_unstable,
useBreadcrumbLink_unstable,
} from './BreadcrumbLink';
export type { BreadcrumbLinkProps, BreadcrumbLinkSlots, BreadcrumbLinkState } from './BreadcrumbLink';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Best practices

### Do

### Don't
Loading