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,7 @@
{
"type": "minor",
"comment": "feat: register items via context",
"packageName": "@fluentui/react-breadcrumb-preview",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ export type BreadcrumbButtonState = ComponentState<BreadcrumbButtonSlots> & Omit
export const breadcrumbClassNames: SlotClassNames<BreadcrumbSlots>;

// @public
export type BreadcrumbContextValue = Required<Pick<BreadcrumbProps, 'appearance' | 'dividerType' | 'size'>>;
export type BreadcrumbContextValues = Required<Pick<BreadcrumbProps, 'appearance' | 'dividerType' | 'size'>> & {
items: Set<BreadcrumbItem_2>;
registerItem: (item: BreadcrumbItem_2) => void;
removeItem: (item: BreadcrumbItem_2) => void;
hasInteractiveItems: boolean;
};

// @public
export const BreadcrumbDivider: ForwardRefComponent<BreadcrumbDividerProps>;
Expand Down Expand Up @@ -79,6 +84,7 @@ export type BreadcrumbItemSlots = {
// @public
export type BreadcrumbItemState = ComponentState<BreadcrumbItemSlots> & Required<Pick<BreadcrumbItemProps, 'size' | 'current'>> & {
isInteractive?: boolean;
hasInteractiveItems?: boolean;
};

// @public
Expand All @@ -90,7 +96,7 @@ export type BreadcrumbProps = ComponentProps<BreadcrumbSlots> & {
};

// @internal (undocumented)
export const BreadcrumbProvider: React_2.Provider<Required<Pick<BreadcrumbProps, "size" | "appearance" | "dividerType">> | undefined>;
export const BreadcrumbProvider: React_2.Provider<BreadcrumbContextValues | undefined>;

// @public (undocumented)
export type BreadcrumbSlots = {
Expand Down Expand Up @@ -149,7 +155,7 @@ export const useBreadcrumbButton_unstable: (props: BreadcrumbButtonProps, ref: R
export const useBreadcrumbButtonStyles_unstable: (state: BreadcrumbButtonState) => BreadcrumbButtonState;

// @internal (undocumented)
export const useBreadcrumbContext_unstable: () => Required<Pick<BreadcrumbProps, "size" | "appearance" | "dividerType">>;
export const useBreadcrumbContext_unstable: () => BreadcrumbContextValues;

// @public
export const useBreadcrumbDivider_unstable: (props: BreadcrumbDividerProps, ref: React_2.Ref<HTMLLIElement>) => BreadcrumbDividerState;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';

export type BreadcrumbItem = {
key: string;
type: 'button' | 'non-interactive';
};

/**
* Data shared between breadcrumb components
*/
export type BreadcrumbContextValue = Required<Pick<BreadcrumbProps, 'appearance' | 'dividerType' | 'size'>>;

export type BreadcrumbContextValues = {
breadcrumb: BreadcrumbContextValue;
export type BreadcrumbContextValues = Required<Pick<BreadcrumbProps, 'appearance' | 'dividerType' | 'size'>> & {
items: Set<BreadcrumbItem>;
registerItem: (item: BreadcrumbItem) => void;
removeItem: (item: BreadcrumbItem) => void;
hasInteractiveItems: boolean;
};

export type BreadcrumbSlots = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import * as React from 'react';
import { BreadcrumbContextValue } from './Breadcrumb.types';
import { BreadcrumbContextValues } from './Breadcrumb.types';

const BreadcrumbContext = React.createContext<BreadcrumbContextValue | undefined>(undefined);
const BreadcrumbContext = React.createContext<BreadcrumbContextValues | undefined>(undefined);

/**
* @internal
*/
export const breadcrumbDefaultValue: BreadcrumbContextValue = {
export const breadcrumbDefaultValue: BreadcrumbContextValues = {
appearance: 'transparent',
size: 'medium',
dividerType: 'chevron',
items: new Set(),
registerItem: () => ({}),
removeItem: () => ({}),
hasInteractiveItems: false,
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const renderBreadcrumb_unstable = (state: BreadcrumbState, contextValues:
assertSlots<BreadcrumbSlots>(state);
return (
<state.root>
<BreadcrumbProvider value={contextValues.breadcrumb}>
<BreadcrumbProvider value={contextValues}>
{state.list && <state.list>{state.root.children}</state.list>}
</BreadcrumbProvider>
</state.root>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
import * as React from 'react';
import type { BreadcrumbContextValues, BreadcrumbState } from './Breadcrumb.types';
import type { BreadcrumbContextValues, BreadcrumbItem, BreadcrumbState } from './Breadcrumb.types';

export function useBreadcrumbContextValues_unstable(state: BreadcrumbState): BreadcrumbContextValues {
const { appearance, dividerType, size } = state;
const [items, setItems] = React.useState<BreadcrumbContextValues['items']>(new Set());

const breadcrumb = React.useMemo(() => {
return { appearance, dividerType, size };
}, [appearance, dividerType, size]);
const registerItem = React.useCallback((item: BreadcrumbItem) => {
setItems(prevItems => {
const newItems = new Set(prevItems);

return { breadcrumb };
newItems.add(item);

return newItems;
});
}, []);

const removeItem = React.useCallback((item: BreadcrumbItem) => {
setItems(prevItems => {
const newItems = new Set(prevItems);

newItems.delete(item);

return newItems;
});
}, []);

const hasInteractiveItems = React.useMemo(() => [...items].some(item => item.type === 'button'), [items]);

return { appearance, dividerType, size, items, registerItem, removeItem, hasInteractiveItems };
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import * as React from 'react';
import type { BreadcrumbButtonProps, BreadcrumbButtonState } from './BreadcrumbButton.types';
import { useButton_unstable } from '@fluentui/react-button';
import { useId } from '@fluentui/react-utilities';
import { useBreadcrumbContext_unstable } from '../Breadcrumb/BreadcrumbContext';
import { BreadcrumbItem } from '../Breadcrumb/Breadcrumb.types';
import type { BreadcrumbButtonProps, BreadcrumbButtonState } from './BreadcrumbButton.types';

/**
* Create the state required to render BreadcrumbButton.
Expand All @@ -16,8 +18,17 @@ export const useBreadcrumbButton_unstable = (
props: BreadcrumbButtonProps,
ref: React.Ref<HTMLButtonElement | HTMLAnchorElement>,
): BreadcrumbButtonState => {
const { appearance, size } = useBreadcrumbContext_unstable();
const { appearance, size, registerItem, removeItem } = useBreadcrumbContext_unstable();
const { current = false, icon, ...rest } = props;
const id = useId('breadcrumb-button-', props.id);

React.useEffect(() => {
const item: BreadcrumbItem = { key: id, type: 'button' };

registerItem(item);

return () => removeItem(item);
}, [id, registerItem, removeItem]);

return {
...useButton_unstable(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ export type BreadcrumbItemState = ComponentState<BreadcrumbItemSlots> &
* Defines whether item is interactive or not.
*/
isInteractive?: boolean;
/**
* Defines whether Breadcrumb type is interactive or not.
*/
hasInteractiveItems?: boolean;
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ export const useBreadcrumbItem_unstable = (
props: BreadcrumbItemProps,
ref: React.Ref<HTMLElement>,
): BreadcrumbItemState => {
const { size } = useBreadcrumbContext_unstable();
const { size, hasInteractiveItems } = useBreadcrumbContext_unstable();
const { current = false, icon } = props;

const isInteractive = typeof props.children === 'object';

const iconSlot = slot.optional(icon, { elementType: 'span' });

return {
components: { root: 'li', icon: 'span' },
root: slot.always(
Expand All @@ -35,5 +35,6 @@ export const useBreadcrumbItem_unstable = (
current,
icon: iconSlot,
isInteractive,
hasInteractiveItems,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const useBreadcrumbItemStyles_unstable = (state: BreadcrumbItemState): Br
large: styles.currentLarge,
} as const;
const noSpacingStyle =
state.isInteractive || (!state.isInteractive && state.size === 'small') ? styles.noSpacing : '';
state.isInteractive || (!state.hasInteractiveItems && state.size === 'small') ? styles.noSpacing : '';

state.root.className = mergeClasses(
breadcrumbItemClassNames.root,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ export {
} from './BreadcrumbButton';
export type { BreadcrumbButtonProps, BreadcrumbButtonSlots, BreadcrumbButtonState } from './BreadcrumbButton';
export { BreadcrumbProvider, useBreadcrumbContext_unstable } from './Breadcrumb';
export type { BreadcrumbContextValue } from './Breadcrumb';
export type { BreadcrumbContextValues } from './Breadcrumb';
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,74 @@ export const Default = () => {
const [breadcrumbType, setBreadcrumbType] = React.useState('item');
return (
<>
<Breadcrumb aria-label="Small Breadcrumb" size={size}>
<BreadcrumbItem>
<BreadcrumbButton>Item 1</BreadcrumbButton>
</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem>
<BreadcrumbButton>Item 2</BreadcrumbButton>
</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem>
<BreadcrumbButton>Item 2</BreadcrumbButton>
</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem>
<BreadcrumbButton current>Item 2</BreadcrumbButton>
</BreadcrumbItem>
</Breadcrumb>
<Breadcrumb aria-label="Small Breadcrumb" size={size}>
<BreadcrumbItem>
<BreadcrumbButton>Item 1</BreadcrumbButton>
</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem>
<BreadcrumbButton>Item 2</BreadcrumbButton>
</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem>
<BreadcrumbButton>Item 2</BreadcrumbButton>
</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem current>Item 4</BreadcrumbItem>
</Breadcrumb>
Interactive
<Breadcrumb aria-label="Small Breadcrumb" size="small">
<BreadcrumbItem>
<BreadcrumbButton>Item 1</BreadcrumbButton>
</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem>
<BreadcrumbButton>Item 2</BreadcrumbButton>
</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem>
<BreadcrumbButton>Item 2</BreadcrumbButton>
</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem current>Item 4</BreadcrumbItem>
</Breadcrumb>
<Breadcrumb aria-label="Small Breadcrumb" size="small">
<BreadcrumbItem>Item 1</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem>Item 2</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem>Item 2</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem current>Item 4</BreadcrumbItem>
</Breadcrumb>
<Breadcrumb aria-label="Small Breadcrumb" size="small">
<BreadcrumbItem>Item 1</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem>Item 2</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem>Item 2</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem>
<BreadcrumbButton current>Item 2</BreadcrumbButton>
</BreadcrumbItem>
</Breadcrumb>
<h2>Playground</h2>
<div style={{ display: 'flex', flexGrow: 2 }}>
<div>
Expand Down Expand Up @@ -96,6 +164,10 @@ export const Default = () => {
</div>
</div>
<Breadcrumb aria-label="Small Breadcrumb" size={size}>
{renderItem({
key: 1000,
value: 'This is another test',
})}
{breadcrumbType === 'item' && items.map(item => renderItem(item))}
{breadcrumbType === 'button' && items.map(item => renderButton(item))}
</Breadcrumb>
Expand Down