diff --git a/change/@fluentui-react-breadcrumb-preview-0abc23c0-2468-4cd2-96f3-99680aafa7e4.json b/change/@fluentui-react-breadcrumb-preview-0abc23c0-2468-4cd2-96f3-99680aafa7e4.json new file mode 100644 index 00000000000000..c23bdd8149c557 --- /dev/null +++ b/change/@fluentui-react-breadcrumb-preview-0abc23c0-2468-4cd2-96f3-99680aafa7e4.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: register items via context", + "packageName": "@fluentui/react-breadcrumb-preview", + "email": "vkozlova@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-breadcrumb-preview/etc/react-breadcrumb-preview.api.md b/packages/react-components/react-breadcrumb-preview/etc/react-breadcrumb-preview.api.md index 122d82704ab6f1..1dee6c01111c69 100644 --- a/packages/react-components/react-breadcrumb-preview/etc/react-breadcrumb-preview.api.md +++ b/packages/react-components/react-breadcrumb-preview/etc/react-breadcrumb-preview.api.md @@ -40,7 +40,12 @@ export type BreadcrumbButtonState = ComponentState & Omit export const breadcrumbClassNames: SlotClassNames; // @public -export type BreadcrumbContextValue = Required>; +export type BreadcrumbContextValues = Required> & { + items: Set; + registerItem: (item: BreadcrumbItem_2) => void; + removeItem: (item: BreadcrumbItem_2) => void; + hasInteractiveItems: boolean; +}; // @public export const BreadcrumbDivider: ForwardRefComponent; @@ -79,6 +84,7 @@ export type BreadcrumbItemSlots = { // @public export type BreadcrumbItemState = ComponentState & Required> & { isInteractive?: boolean; + hasInteractiveItems?: boolean; }; // @public @@ -90,7 +96,7 @@ export type BreadcrumbProps = ComponentProps & { }; // @internal (undocumented) -export const BreadcrumbProvider: React_2.Provider> | undefined>; +export const BreadcrumbProvider: React_2.Provider; // @public (undocumented) export type BreadcrumbSlots = { @@ -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>; +export const useBreadcrumbContext_unstable: () => BreadcrumbContextValues; // @public export const useBreadcrumbDivider_unstable: (props: BreadcrumbDividerProps, ref: React_2.Ref) => BreadcrumbDividerState; diff --git a/packages/react-components/react-breadcrumb-preview/src/components/Breadcrumb/Breadcrumb.types.ts b/packages/react-components/react-breadcrumb-preview/src/components/Breadcrumb/Breadcrumb.types.ts index 39eb1e06e18a61..528f1e199496c6 100644 --- a/packages/react-components/react-breadcrumb-preview/src/components/Breadcrumb/Breadcrumb.types.ts +++ b/packages/react-components/react-breadcrumb-preview/src/components/Breadcrumb/Breadcrumb.types.ts @@ -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>; - -export type BreadcrumbContextValues = { - breadcrumb: BreadcrumbContextValue; +export type BreadcrumbContextValues = Required> & { + items: Set; + registerItem: (item: BreadcrumbItem) => void; + removeItem: (item: BreadcrumbItem) => void; + hasInteractiveItems: boolean; }; export type BreadcrumbSlots = { diff --git a/packages/react-components/react-breadcrumb-preview/src/components/Breadcrumb/BreadcrumbContext.ts b/packages/react-components/react-breadcrumb-preview/src/components/Breadcrumb/BreadcrumbContext.ts index 7e77affbeb630b..d2c9a23e1e5e52 100644 --- a/packages/react-components/react-breadcrumb-preview/src/components/Breadcrumb/BreadcrumbContext.ts +++ b/packages/react-components/react-breadcrumb-preview/src/components/Breadcrumb/BreadcrumbContext.ts @@ -1,15 +1,19 @@ import * as React from 'react'; -import { BreadcrumbContextValue } from './Breadcrumb.types'; +import { BreadcrumbContextValues } from './Breadcrumb.types'; -const BreadcrumbContext = React.createContext(undefined); +const BreadcrumbContext = React.createContext(undefined); /** * @internal */ -export const breadcrumbDefaultValue: BreadcrumbContextValue = { +export const breadcrumbDefaultValue: BreadcrumbContextValues = { appearance: 'transparent', size: 'medium', dividerType: 'chevron', + items: new Set(), + registerItem: () => ({}), + removeItem: () => ({}), + hasInteractiveItems: false, }; /** diff --git a/packages/react-components/react-breadcrumb-preview/src/components/Breadcrumb/renderBreadcrumb.tsx b/packages/react-components/react-breadcrumb-preview/src/components/Breadcrumb/renderBreadcrumb.tsx index bbe9a51d305321..0ec4450881be82 100644 --- a/packages/react-components/react-breadcrumb-preview/src/components/Breadcrumb/renderBreadcrumb.tsx +++ b/packages/react-components/react-breadcrumb-preview/src/components/Breadcrumb/renderBreadcrumb.tsx @@ -11,7 +11,7 @@ export const renderBreadcrumb_unstable = (state: BreadcrumbState, contextValues: assertSlots(state); return ( - + {state.list && {state.root.children}} diff --git a/packages/react-components/react-breadcrumb-preview/src/components/Breadcrumb/useBreadcrumbContextValue.ts b/packages/react-components/react-breadcrumb-preview/src/components/Breadcrumb/useBreadcrumbContextValue.ts index 9a89406fea807a..ec8e9a235cf05d 100644 --- a/packages/react-components/react-breadcrumb-preview/src/components/Breadcrumb/useBreadcrumbContextValue.ts +++ b/packages/react-components/react-breadcrumb-preview/src/components/Breadcrumb/useBreadcrumbContextValue.ts @@ -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(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 }; } diff --git a/packages/react-components/react-breadcrumb-preview/src/components/BreadcrumbButton/useBreadcrumbButton.ts b/packages/react-components/react-breadcrumb-preview/src/components/BreadcrumbButton/useBreadcrumbButton.ts index 39bad61f182588..1578467ef67929 100644 --- a/packages/react-components/react-breadcrumb-preview/src/components/BreadcrumbButton/useBreadcrumbButton.ts +++ b/packages/react-components/react-breadcrumb-preview/src/components/BreadcrumbButton/useBreadcrumbButton.ts @@ -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. @@ -16,8 +18,17 @@ export const useBreadcrumbButton_unstable = ( props: BreadcrumbButtonProps, ref: React.Ref, ): 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( diff --git a/packages/react-components/react-breadcrumb-preview/src/components/BreadcrumbItem/BreadcrumbItem.types.ts b/packages/react-components/react-breadcrumb-preview/src/components/BreadcrumbItem/BreadcrumbItem.types.ts index 4e43afa0c6c671..7a9409cd39cd1e 100644 --- a/packages/react-components/react-breadcrumb-preview/src/components/BreadcrumbItem/BreadcrumbItem.types.ts +++ b/packages/react-components/react-breadcrumb-preview/src/components/BreadcrumbItem/BreadcrumbItem.types.ts @@ -32,4 +32,8 @@ export type BreadcrumbItemState = ComponentState & * Defines whether item is interactive or not. */ isInteractive?: boolean; + /** + * Defines whether Breadcrumb type is interactive or not. + */ + hasInteractiveItems?: boolean; }; diff --git a/packages/react-components/react-breadcrumb-preview/src/components/BreadcrumbItem/useBreadcrumbItem.ts b/packages/react-components/react-breadcrumb-preview/src/components/BreadcrumbItem/useBreadcrumbItem.ts index 57c660f0022665..ec5b5c511e2186 100644 --- a/packages/react-components/react-breadcrumb-preview/src/components/BreadcrumbItem/useBreadcrumbItem.ts +++ b/packages/react-components/react-breadcrumb-preview/src/components/BreadcrumbItem/useBreadcrumbItem.ts @@ -16,12 +16,12 @@ export const useBreadcrumbItem_unstable = ( props: BreadcrumbItemProps, ref: React.Ref, ): 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( @@ -35,5 +35,6 @@ export const useBreadcrumbItem_unstable = ( current, icon: iconSlot, isInteractive, + hasInteractiveItems, }; }; diff --git a/packages/react-components/react-breadcrumb-preview/src/components/BreadcrumbItem/useBreadcrumbItemStyles.styles.ts b/packages/react-components/react-breadcrumb-preview/src/components/BreadcrumbItem/useBreadcrumbItemStyles.styles.ts index 57494004dd1671..89b6b314dcd104 100644 --- a/packages/react-components/react-breadcrumb-preview/src/components/BreadcrumbItem/useBreadcrumbItemStyles.styles.ts +++ b/packages/react-components/react-breadcrumb-preview/src/components/BreadcrumbItem/useBreadcrumbItemStyles.styles.ts @@ -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, diff --git a/packages/react-components/react-breadcrumb-preview/src/index.ts b/packages/react-components/react-breadcrumb-preview/src/index.ts index 72da4121c2b3cd..563d3928982a64 100644 --- a/packages/react-components/react-breadcrumb-preview/src/index.ts +++ b/packages/react-components/react-breadcrumb-preview/src/index.ts @@ -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'; diff --git a/packages/react-components/react-breadcrumb-preview/stories/Breadcrumb/BreadcrumbDefault.stories.tsx b/packages/react-components/react-breadcrumb-preview/stories/Breadcrumb/BreadcrumbDefault.stories.tsx index dca17bc3f02d73..e5655042de43ef 100644 --- a/packages/react-components/react-breadcrumb-preview/stories/Breadcrumb/BreadcrumbDefault.stories.tsx +++ b/packages/react-components/react-breadcrumb-preview/stories/Breadcrumb/BreadcrumbDefault.stories.tsx @@ -69,6 +69,74 @@ export const Default = () => { const [breadcrumbType, setBreadcrumbType] = React.useState('item'); return ( <> + + + Item 1 + + + + Item 2 + + + + Item 2 + + + + Item 2 + + + + + Item 1 + + + + Item 2 + + + + Item 2 + + + Item 4 + + Interactive + + + Item 1 + + + + Item 2 + + + + Item 2 + + + Item 4 + + + Item 1 + + Item 2 + + Item 2 + + Item 4 + + + Item 1 + + Item 2 + + Item 2 + + + Item 2 + +

Playground

@@ -96,6 +164,10 @@ export const Default = () => {
+ {renderItem({ + key: 1000, + value: 'This is another test', + })} {breadcrumbType === 'item' && items.map(item => renderItem(item))} {breadcrumbType === 'button' && items.map(item => renderButton(item))}