From 77666520b78ce564f9a1ad6f3c4677024b38aabf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:11:04 +0000 Subject: [PATCH 1/5] Initial plan From c22ad92ec188bf6d048d24e7d02564a32ec497ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:17:14 +0000 Subject: [PATCH 2/5] fix(react-tree): support indentation for TreeItem levels > 10 via inline CSS variable Agent-Logs-Url: https://github.com/microsoft/fluentui/sessions/9f80c711-ce28-4674-af00-9dcf00d323b7 Co-authored-by: tudorpopams <97875118+tudorpopams@users.noreply.github.com> --- ...uentui-react-tree-tree-deep-level-fix.json | 7 ++++ .../src/components/TreeItem/TreeItem.test.tsx | 35 +++++++++++++++++++ .../TreeItem/useTreeItemStyles.styles.ts | 11 ++++++ 3 files changed, 53 insertions(+) create mode 100644 change/@fluentui-react-tree-tree-deep-level-fix.json diff --git a/change/@fluentui-react-tree-tree-deep-level-fix.json b/change/@fluentui-react-tree-tree-deep-level-fix.json new file mode 100644 index 0000000000000..ac7e881abae4b --- /dev/null +++ b/change/@fluentui-react-tree-tree-deep-level-fix.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix(react-tree): support indentation for TreeItem levels greater than 10 via inline CSS variable fallback", + "packageName": "@fluentui/react-tree", + "email": "198982749+Copilot@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-tree/library/src/components/TreeItem/TreeItem.test.tsx b/packages/react-components/react-tree/library/src/components/TreeItem/TreeItem.test.tsx index a23f3ca2f506c..24cd6eb305f3d 100644 --- a/packages/react-components/react-tree/library/src/components/TreeItem/TreeItem.test.tsx +++ b/packages/react-components/react-tree/library/src/components/TreeItem/TreeItem.test.tsx @@ -5,6 +5,7 @@ import { isConformant } from '../../testing/isConformant'; import type { TreeItemProps } from './TreeItem.types'; import { treeItemClassNames } from './useTreeItemStyles.styles'; import { Tree } from '../../Tree'; +import { treeItemLevelToken } from '../../utils/tokens'; describe('TreeItem', () => { isConformant({ @@ -51,4 +52,38 @@ describe('TreeItem', () => { fireEvent.click(result.getByText('Default TreeItem')); expect(handleOpenChange).not.toHaveBeenCalled(); }); + + it('should set the level CSS variable via inline style for levels greater than 10', () => { + const depth = 12; + const openItems: string[] = []; + const renderNestedTree = (current: number): React.ReactElement => { + if (current > 1) { + openItems.push(`item-${current}`); + } + return ( + + {`level ${current}`} + {current > 1 ? {renderNestedTree(current - 1)} : null} + + ); + }; + const treeContent = renderNestedTree(depth); + + const result = render({treeContent}); + + const treeItems = result.container.querySelectorAll(`.${treeItemClassNames.root}`); + expect(treeItems).toHaveLength(depth); + + // Levels 1..10 are handled by static classes and should not set the inline CSS variable + // Levels > 10 fall back to an inline CSS variable for the indentation + treeItems.forEach(item => { + const ariaLevel = Number(item.getAttribute('aria-level')); + const inlineLevel = item.style.getPropertyValue(treeItemLevelToken); + if (ariaLevel > 10) { + expect(inlineLevel).toBe(String(ariaLevel)); + } else { + expect(inlineLevel).toBe(''); + } + }); + }); }); diff --git a/packages/react-components/react-tree/library/src/components/TreeItem/useTreeItemStyles.styles.ts b/packages/react-components/react-tree/library/src/components/TreeItem/useTreeItemStyles.styles.ts index adc5d672a8528..fa990acdbe28a 100644 --- a/packages/react-components/react-tree/library/src/components/TreeItem/useTreeItemStyles.styles.ts +++ b/packages/react-components/react-tree/library/src/components/TreeItem/useTreeItemStyles.styles.ts @@ -77,6 +77,17 @@ export const useTreeItemStyles_unstable = (state: TreeItemState): TreeItemState state.root.className, ); + // For levels beyond the statically generated classes (> 10), fall back to an + // inline style that sets the indentation CSS variable dynamically. This avoids + // generating an unbounded number of atomic classes while still supporting + // arbitrarily deep trees. User-provided inline styles take precedence. + if (!isStaticallyDefinedLevel(level)) { + state.root.style = { + [treeItemLevelToken]: level, + ...state.root.style, + }; + } + return state; }; From 83932d7ece368b45bd3c686f088a9090fbbb1a1c Mon Sep 17 00:00:00 2001 From: Dmytro Kirpa Date: Tue, 21 Apr 2026 11:58:40 +0200 Subject: [PATCH 3/5] feat(react-message-bar): add useMessageBarBase_unstable hook (#35908) Co-authored-by: Claude Sonnet 4.6 Co-authored-by: tudorpopams <97875118+tudorpopams@users.noreply.github.com> --- ...-4a6b8620-f010-4128-b5a0-4c68e73bc7de.json | 7 +++ .../library/etc/react-message-bar.api.md | 22 +++++++++- .../library/src/MessageBar.ts | 3 ++ .../library/src/MessageBarBody.ts | 1 + .../components/MessageBar/MessageBar.types.ts | 5 ++- .../src/components/MessageBar/index.ts | 4 +- .../MessageBar/renderMessageBar.tsx | 7 ++- .../MessageBar/useMessageBar.test.tsx | 40 +++++++++++++++++ .../components/MessageBar/useMessageBar.ts | 44 ++++++++++++++----- .../src/components/MessageBarBody/index.ts | 1 + .../react-message-bar/library/src/index.ts | 11 ++++- 11 files changed, 129 insertions(+), 16 deletions(-) create mode 100644 change/@fluentui-react-message-bar-4a6b8620-f010-4128-b5a0-4c68e73bc7de.json create mode 100644 packages/react-components/react-message-bar/library/src/components/MessageBar/useMessageBar.test.tsx diff --git a/change/@fluentui-react-message-bar-4a6b8620-f010-4128-b5a0-4c68e73bc7de.json b/change/@fluentui-react-message-bar-4a6b8620-f010-4128-b5a0-4c68e73bc7de.json new file mode 100644 index 0000000000000..af4299b40e09a --- /dev/null +++ b/change/@fluentui-react-message-bar-4a6b8620-f010-4128-b5a0-4c68e73bc7de.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat(react-message-bar): add useMessageBarBase_unstable hook", + "packageName": "@fluentui/react-message-bar", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-message-bar/library/etc/react-message-bar.api.md b/packages/react-components/react-message-bar/library/etc/react-message-bar.api.md index db2ebad406152..b551006eddc15 100644 --- a/packages/react-components/react-message-bar/library/etc/react-message-bar.api.md +++ b/packages/react-components/react-message-bar/library/etc/react-message-bar.api.md @@ -7,6 +7,7 @@ import type { ButtonContextValue } from '@fluentui/react-button'; import type { ComponentProps } from '@fluentui/react-utilities'; import type { ComponentState } from '@fluentui/react-utilities'; +import type { DistributiveOmit } from '@fluentui/react-utilities'; import type { ForwardRefComponent } from '@fluentui/react-utilities'; import type { JSXElement } from '@fluentui/react-utilities'; import * as React_2 from 'react'; @@ -41,12 +42,25 @@ export type MessageBarActionsState = ComponentState & Pi hasActions: boolean; }; +// @public (undocumented) +export type MessageBarBaseProps = DistributiveOmit; + +// @public (undocumented) +export type MessageBarBaseState = DistributiveOmit; + // @public export const MessageBarBody: ForwardRefComponent; // @public (undocumented) export const messageBarBodyClassNames: SlotClassNames; +// @public (undocumented) +export type MessageBarBodyContextValues = { + link: { + inline?: boolean; + }; +}; + // @public export type MessageBarBodyProps = ComponentProps; @@ -156,7 +170,7 @@ export type MessageBarTransitionContextValue = { }; // @public -export const renderMessageBar_unstable: (state: MessageBarState, contexts: MessageBarContextValues) => JSXElement; +export const renderMessageBar_unstable: (state: MessageBarBaseState, contexts: MessageBarContextValues) => JSXElement; // @public export const renderMessageBarActions_unstable: (state: MessageBarActionsState, contexts: MessageBarActionsContextValues) => JSXElement; @@ -182,9 +196,15 @@ export function useMessageBarActionsContextValue_unstable(): MessageBarActionsCo // @public export const useMessageBarActionsStyles_unstable: (state: MessageBarActionsState) => MessageBarActionsState; +// @public +export const useMessageBarBase_unstable: (props: MessageBarBaseProps, ref: React_2.Ref) => MessageBarBaseState; + // @public export const useMessageBarBody_unstable: (props: MessageBarBodyProps, ref: React_2.Ref) => MessageBarBodyState; +// @public (undocumented) +export function useMessageBarBodyContextValues_unstable(state: MessageBarBodyState): MessageBarBodyContextValues; + // @public export const useMessageBarBodyStyles_unstable: (state: MessageBarBodyState) => MessageBarBodyState; diff --git a/packages/react-components/react-message-bar/library/src/MessageBar.ts b/packages/react-components/react-message-bar/library/src/MessageBar.ts index ffb91018f6786..3a82d86b4182b 100644 --- a/packages/react-components/react-message-bar/library/src/MessageBar.ts +++ b/packages/react-components/react-message-bar/library/src/MessageBar.ts @@ -1,4 +1,6 @@ export type { + MessageBarBaseProps, + MessageBarBaseState, MessageBarContextValues, MessageBarIntent, MessageBarProps, @@ -11,5 +13,6 @@ export { renderMessageBar_unstable, useMessageBarContextValue_unstable, useMessageBarStyles_unstable, + useMessageBarBase_unstable, useMessageBar_unstable, } from './components/MessageBar/index'; diff --git a/packages/react-components/react-message-bar/library/src/MessageBarBody.ts b/packages/react-components/react-message-bar/library/src/MessageBarBody.ts index 6b36a72d2517b..8955ba3b1027d 100644 --- a/packages/react-components/react-message-bar/library/src/MessageBarBody.ts +++ b/packages/react-components/react-message-bar/library/src/MessageBarBody.ts @@ -10,4 +10,5 @@ export { renderMessageBarBody_unstable, useMessageBarBodyStyles_unstable, useMessageBarBody_unstable, + useMessageBarBodyContextValues_unstable, } from './components/MessageBarBody/index'; diff --git a/packages/react-components/react-message-bar/library/src/components/MessageBar/MessageBar.types.ts b/packages/react-components/react-message-bar/library/src/components/MessageBar/MessageBar.types.ts index 95b0ee35ccf3d..7aad7eb809e94 100644 --- a/packages/react-components/react-message-bar/library/src/components/MessageBar/MessageBar.types.ts +++ b/packages/react-components/react-message-bar/library/src/components/MessageBar/MessageBar.types.ts @@ -1,4 +1,4 @@ -import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import type { ComponentProps, ComponentState, DistributiveOmit, Slot } from '@fluentui/react-utilities'; import type { MessageBarContextValue } from '../../contexts/messageBarContext'; export type MessageBarSlots = { @@ -53,3 +53,6 @@ export type MessageBarState = ComponentState & */ transitionClassName: string; }; + +export type MessageBarBaseProps = DistributiveOmit; +export type MessageBarBaseState = DistributiveOmit; diff --git a/packages/react-components/react-message-bar/library/src/components/MessageBar/index.ts b/packages/react-components/react-message-bar/library/src/components/MessageBar/index.ts index 5cb0b03985cb3..6da118dcb122b 100644 --- a/packages/react-components/react-message-bar/library/src/components/MessageBar/index.ts +++ b/packages/react-components/react-message-bar/library/src/components/MessageBar/index.ts @@ -1,5 +1,7 @@ export { MessageBar } from './MessageBar'; export type { + MessageBarBaseProps, + MessageBarBaseState, MessageBarContextValues, MessageBarIntent, MessageBarProps, @@ -7,6 +9,6 @@ export type { MessageBarState, } from './MessageBar.types'; export { renderMessageBar_unstable } from './renderMessageBar'; -export { useMessageBar_unstable } from './useMessageBar'; +export { useMessageBarBase_unstable, useMessageBar_unstable } from './useMessageBar'; export { messageBarClassNames, useMessageBarStyles_unstable } from './useMessageBarStyles.styles'; export { useMessageBarContextValue_unstable } from './useMessageBarContextValues'; diff --git a/packages/react-components/react-message-bar/library/src/components/MessageBar/renderMessageBar.tsx b/packages/react-components/react-message-bar/library/src/components/MessageBar/renderMessageBar.tsx index 2d81b61e34227..e3470ff1b12e0 100644 --- a/packages/react-components/react-message-bar/library/src/components/MessageBar/renderMessageBar.tsx +++ b/packages/react-components/react-message-bar/library/src/components/MessageBar/renderMessageBar.tsx @@ -3,13 +3,16 @@ import { assertSlots } from '@fluentui/react-utilities'; import type { JSXElement } from '@fluentui/react-utilities'; -import type { MessageBarState, MessageBarSlots, MessageBarContextValues } from './MessageBar.types'; +import type { MessageBarBaseState, MessageBarSlots, MessageBarContextValues } from './MessageBar.types'; import { MessageBarContextProvider } from '../../contexts/messageBarContext'; /** * Render the final JSX of MessageBar */ -export const renderMessageBar_unstable = (state: MessageBarState, contexts: MessageBarContextValues): JSXElement => { +export const renderMessageBar_unstable = ( + state: MessageBarBaseState, + contexts: MessageBarContextValues, +): JSXElement => { assertSlots(state); return ( diff --git a/packages/react-components/react-message-bar/library/src/components/MessageBar/useMessageBar.test.tsx b/packages/react-components/react-message-bar/library/src/components/MessageBar/useMessageBar.test.tsx new file mode 100644 index 0000000000000..57236241bc805 --- /dev/null +++ b/packages/react-components/react-message-bar/library/src/components/MessageBar/useMessageBar.test.tsx @@ -0,0 +1,40 @@ +import * as React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; + +import { useMessageBar_unstable } from './useMessageBar'; + +describe('useMessageBar', () => { + it('should return state for default props', () => { + const { result } = renderHook(() => useMessageBar_unstable({}, React.createRef())); + + expect(result.current).toMatchObject({ + intent: 'info', + layout: 'singleline', + shape: 'rounded', + }); + + expect(result.current.icon?.children).toBeDefined(); + }); + + it('should return state for custom props', () => { + const { result } = renderHook(() => + useMessageBar_unstable( + { + intent: 'error', + layout: 'multiline', + shape: 'square', + icon: { children: null }, + }, + React.createRef(), + ), + ); + + expect(result.current).toMatchObject({ + intent: 'error', + layout: 'multiline', + shape: 'square', + }); + + expect(result.current.icon?.children).toBeNull(); + }); +}); diff --git a/packages/react-components/react-message-bar/library/src/components/MessageBar/useMessageBar.ts b/packages/react-components/react-message-bar/library/src/components/MessageBar/useMessageBar.ts index b32153dd1a2b5..4d590ac4b8142 100644 --- a/packages/react-components/react-message-bar/library/src/components/MessageBar/useMessageBar.ts +++ b/packages/react-components/react-message-bar/library/src/components/MessageBar/useMessageBar.ts @@ -3,23 +3,23 @@ import * as React from 'react'; import { getIntrinsicElementProps, slot, useId, useMergedRefs } from '@fluentui/react-utilities'; import { useAnnounce } from '@fluentui/react-shared-contexts'; -import type { MessageBarProps, MessageBarState } from './MessageBar.types'; +import type { MessageBarBaseProps, MessageBarBaseState, MessageBarProps, MessageBarState } from './MessageBar.types'; import { getIntentIcon } from './getIntentIcon'; import { useMessageBarReflow } from './useMessageBarReflow'; import { useMessageBarTransitionContext } from '../../contexts/messageBarTransitionContext'; import { useMotionForwardedRef } from '@fluentui/react-motion'; /** - * Create the state required to render MessageBar. - * - * The returned state can be modified with hooks such as useMessageBarStyles_unstable, - * before being passed to renderMessageBar_unstable. + * Create the base state required to render MessageBar without design-specific props. * - * @param props - props from this instance of MessageBar + * @param props - props from this instance of MessageBar (without shape) * @param ref - reference to root HTMLElement of MessageBar */ -export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref): MessageBarState => { - const { layout = 'auto', intent = 'info', politeness, shape = 'rounded' } = props; +export const useMessageBarBase_unstable = ( + props: MessageBarBaseProps, + ref: React.Ref, +): MessageBarBaseState => { + const { layout = 'auto', intent = 'info', politeness } = props; const computedPoliteness = politeness ?? intent === 'info' ? 'polite' : 'assertive'; const autoReflow = layout === 'auto'; const { ref: reflowRef, reflowing } = useMessageBarReflow(autoReflow); @@ -57,11 +57,9 @@ export const useMessageBar_unstable = (props: MessageBarProps, ref: React.Ref): MessageBarState => { + const { shape = 'rounded', ...baseProps } = props; + + const state = useMessageBarBase_unstable(baseProps, ref); + + return { + ...state, shape, + icon: slot.optional(props.icon, { + defaultProps: { + children: getIntentIcon(state.intent), + }, + renderByDefault: true, + elementType: 'div', + }), }; }; diff --git a/packages/react-components/react-message-bar/library/src/components/MessageBarBody/index.ts b/packages/react-components/react-message-bar/library/src/components/MessageBarBody/index.ts index 70cdf388b9395..b4e71a2dbb5ea 100644 --- a/packages/react-components/react-message-bar/library/src/components/MessageBarBody/index.ts +++ b/packages/react-components/react-message-bar/library/src/components/MessageBarBody/index.ts @@ -7,4 +7,5 @@ export type { } from './MessageBarBody.types'; export { renderMessageBarBody_unstable } from './renderMessageBarBody'; export { useMessageBarBody_unstable } from './useMessageBarBody'; +export { useMessageBarBodyContextValues_unstable } from './useMessageBarBodyContextValues'; export { messageBarBodyClassNames, useMessageBarBodyStyles_unstable } from './useMessageBarBodyStyles.styles'; diff --git a/packages/react-components/react-message-bar/library/src/index.ts b/packages/react-components/react-message-bar/library/src/index.ts index 0bc4ab549edc8..ce5a8ce2db6df 100644 --- a/packages/react-components/react-message-bar/library/src/index.ts +++ b/packages/react-components/react-message-bar/library/src/index.ts @@ -1,6 +1,7 @@ export { MessageBar, useMessageBarStyles_unstable, + useMessageBarBase_unstable, useMessageBar_unstable, useMessageBarContextValue_unstable, renderMessageBar_unstable, @@ -8,6 +9,8 @@ export { } from './MessageBar'; export type { + MessageBarBaseProps, + MessageBarBaseState, MessageBarProps, MessageBarSlots, MessageBarState, @@ -45,11 +48,17 @@ export { MessageBarBody, useMessageBarBodyStyles_unstable, useMessageBarBody_unstable, + useMessageBarBodyContextValues_unstable, renderMessageBarBody_unstable, messageBarBodyClassNames, } from './MessageBarBody'; -export type { MessageBarBodyProps, MessageBarBodySlots, MessageBarBodyState } from './MessageBarBody'; +export type { + MessageBarBodyProps, + MessageBarBodySlots, + MessageBarBodyState, + MessageBarBodyContextValues, +} from './MessageBarBody'; export { MessageBarContextProvider, From c8c8692f74595a9c219c89c26a75c29081c2df00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:11:04 +0000 Subject: [PATCH 4/5] Initial plan Co-authored-by: tudorpopams <97875118+tudorpopams@users.noreply.github.com> From a870a5479daf8cc1f9ae265d5aaa4400761657e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:18:57 +0000 Subject: [PATCH 5/5] docs(react-tree): update InlineStylingTreeItemLevel story description to reflect automatic fallback Agent-Logs-Url: https://github.com/microsoft/fluentui/sessions/b7e51df0-83b9-4c97-b737-0c1922a87851 Co-authored-by: tudorpopams <97875118+tudorpopams@users.noreply.github.com> --- .../stories/src/Tree/TreeInlineStylingTreeItemLevel.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-tree/stories/src/Tree/TreeInlineStylingTreeItemLevel.stories.tsx b/packages/react-components/react-tree/stories/src/Tree/TreeInlineStylingTreeItemLevel.stories.tsx index 4f054505663ff..060a7571b92c9 100644 --- a/packages/react-components/react-tree/stories/src/Tree/TreeInlineStylingTreeItemLevel.stories.tsx +++ b/packages/react-components/react-tree/stories/src/Tree/TreeInlineStylingTreeItemLevel.stories.tsx @@ -28,7 +28,7 @@ InlineStylingTreeItemLevel.parameters = { docs: { description: { story: ` -The tree component supports nested styling up to 10 levels, limited by performance considerations. For more than 10 levels of nesting, use dynamic styling instead. Below is an example of how to apply custom inline styles to create dynamic tree item levels, overriding the default static styles. +The tree component generates static styles for the first 10 nesting levels (for performance reasons) and automatically falls back to an inline CSS variable for deeper levels, so arbitrarily deep trees indent correctly out of the box. Below is an example of how to apply custom inline styles to create dynamic tree item levels, overriding the default static styles. `, }, },