From b5b3207ad5651a93552c5376e782534f18a317c3 Mon Sep 17 00:00:00 2001 From: mainframev Date: Mon, 10 Aug 2026 11:30:48 +0200 Subject: [PATCH] fix(react-link): remove inline prop from base hook --- ...-70273175-18df-48c3-b42f-26b8aaf364f0.json | 7 +++ .../stories/src/Link/LinkDefault.stories.tsx | 4 +- .../stories/src/Link/LinkDescription.md | 2 +- .../MessageBar/MessageBarDefault.stories.tsx | 2 +- .../MessageBar/MessageBarIntent.stories.tsx | 2 +- .../react-link/library/etc/react-link.api.md | 4 +- .../library/src/components/Link/Link.types.ts | 8 +-- .../src/components/Link/useLink.test.tsx | 49 +++++++++++++++++++ .../library/src/components/Link/useLink.ts | 10 ++-- 9 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 change/@fluentui-react-link-70273175-18df-48c3-b42f-26b8aaf364f0.json create mode 100644 packages/react-components/react-link/library/src/components/Link/useLink.test.tsx diff --git a/change/@fluentui-react-link-70273175-18df-48c3-b42f-26b8aaf364f0.json b/change/@fluentui-react-link-70273175-18df-48c3-b42f-26b8aaf364f0.json new file mode 100644 index 0000000000000..6c0c2aa59799f --- /dev/null +++ b/change/@fluentui-react-link-70273175-18df-48c3-b42f-26b8aaf364f0.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: move inline styling and context resolution out of the base hook", + "packageName": "@fluentui/react-link", + "email": "vgenaev@gmail.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-headless-components-preview/stories/src/Link/LinkDefault.stories.tsx b/packages/react-components/react-headless-components-preview/stories/src/Link/LinkDefault.stories.tsx index f69452e2b9203..b2ece5504e3e8 100644 --- a/packages/react-components/react-headless-components-preview/stories/src/Link/LinkDefault.stories.tsx +++ b/packages/react-components/react-headless-components-preview/stories/src/Link/LinkDefault.stories.tsx @@ -10,11 +10,11 @@ export const Default = (): React.ReactNode => (

By continuing you agree to our{' '} - + Terms of Service {' '} and{' '} - + Privacy Policy . diff --git a/packages/react-components/react-headless-components-preview/stories/src/Link/LinkDescription.md b/packages/react-components/react-headless-components-preview/stories/src/Link/LinkDescription.md index a7cdacb18259f..ea6918bf61c44 100644 --- a/packages/react-components/react-headless-components-preview/stories/src/Link/LinkDescription.md +++ b/packages/react-components/react-headless-components-preview/stories/src/Link/LinkDescription.md @@ -1,3 +1,3 @@ A link navigates the user to another page, section, or resource. -Use Link for navigation actions and Button for in-page operations. Links support an `inline` prop that renders them within a text flow, and a `disabled` prop that prevents navigation while preserving accessibility. +Use Link for navigation actions and Button for in-page operations. Links support a `disabled` prop that prevents navigation while preserving accessibility. diff --git a/packages/react-components/react-headless-components-preview/stories/src/MessageBar/MessageBarDefault.stories.tsx b/packages/react-components/react-headless-components-preview/stories/src/MessageBar/MessageBarDefault.stories.tsx index 117f14f4502b6..b5d30292b705f 100644 --- a/packages/react-components/react-headless-components-preview/stories/src/MessageBar/MessageBarDefault.stories.tsx +++ b/packages/react-components/react-headless-components-preview/stories/src/MessageBar/MessageBarDefault.stories.tsx @@ -18,7 +18,7 @@ export const Default = (): React.ReactNode => ( Descriptive title Message providing information to the user with actionable insights.{' '} - + Learn more diff --git a/packages/react-components/react-headless-components-preview/stories/src/MessageBar/MessageBarIntent.stories.tsx b/packages/react-components/react-headless-components-preview/stories/src/MessageBar/MessageBarIntent.stories.tsx index c998815da7a51..b9db05963aa38 100644 --- a/packages/react-components/react-headless-components-preview/stories/src/MessageBar/MessageBarIntent.stories.tsx +++ b/packages/react-components/react-headless-components-preview/stories/src/MessageBar/MessageBarIntent.stories.tsx @@ -44,7 +44,7 @@ export const Intent = (): React.ReactNode => ( {item.title} Message providing information to the user with actionable insights.{' '} - + Learn more diff --git a/packages/react-components/react-link/library/etc/react-link.api.md b/packages/react-components/react-link/library/etc/react-link.api.md index 8bb58f3bd9285..e5f9003a00934 100644 --- a/packages/react-components/react-link/library/etc/react-link.api.md +++ b/packages/react-components/react-link/library/etc/react-link.api.md @@ -18,10 +18,10 @@ import type { SlotClassNames } from '@fluentui/react-utilities'; export const Link: ForwardRefComponent; // @public -export type LinkBaseProps = DistributiveOmit; +export type LinkBaseProps = DistributiveOmit; // @public -export type LinkBaseState = DistributiveOmit; +export type LinkBaseState = DistributiveOmit; // @public (undocumented) export const linkClassNames: SlotClassNames; diff --git a/packages/react-components/react-link/library/src/components/Link/Link.types.ts b/packages/react-components/react-link/library/src/components/Link/Link.types.ts index 915a86c312535..fc75eb723ccb0 100644 --- a/packages/react-components/react-link/library/src/components/Link/Link.types.ts +++ b/packages/react-components/react-link/library/src/components/Link/Link.types.ts @@ -37,10 +37,10 @@ export type LinkProps = ComponentProps & { }; /** - * Link props without design-specific props (appearance). + * Link props without design-specific props (appearance, inline). * Use this when building a base link that is unstyled or uses a custom design system. */ -export type LinkBaseProps = DistributiveOmit; +export type LinkBaseProps = DistributiveOmit; export type LinkState = ComponentState & Required> & { @@ -48,6 +48,6 @@ export type LinkState = ComponentState & }; /** - * Link state without design-specific state (appearance, backgroundAppearance). + * Link state without design-specific state (appearance, backgroundAppearance, inline). */ -export type LinkBaseState = DistributiveOmit; +export type LinkBaseState = DistributiveOmit; diff --git a/packages/react-components/react-link/library/src/components/Link/useLink.test.tsx b/packages/react-components/react-link/library/src/components/Link/useLink.test.tsx new file mode 100644 index 0000000000000..6e9fea6cc1640 --- /dev/null +++ b/packages/react-components/react-link/library/src/components/Link/useLink.test.tsx @@ -0,0 +1,49 @@ +import * as React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; + +import { LinkContextProvider } from '../../contexts/linkContext'; +import type { LinkBaseState } from './Link.types'; +import { useLink_unstable, useLinkBase_unstable } from './useLink'; + +const useLinkBaseTypeTests = () => { + // @ts-expect-error - inline is a styled Link prop, not a LinkBase prop + useLinkBase_unstable({ inline: true }, null); + + const state: LinkBaseState = useLinkBase_unstable({}, null); + // @ts-expect-error - inline is not exposed by LinkBaseState + state.inline; +}; + +useLinkBaseTypeTests; + +describe('useLink_unstable', () => { + it('defaults inline to false', () => { + const { result } = renderHook(() => useLink_unstable({}, null)); + + expect(result.current.inline).toBe(false); + }); + + it('sets inline from props', () => { + const { result } = renderHook(() => useLink_unstable({ inline: true }, null)); + + expect(result.current.inline).toBe(true); + }); + + it('sets inline from context when the prop is absent', () => { + const wrapper = ({ children }: { children?: React.ReactNode }) => ( + {children} + ); + const { result } = renderHook(() => useLink_unstable({}, null), { wrapper }); + + expect(result.current.inline).toBe(true); + }); + + it('prefers an explicit inline prop over context', () => { + const wrapper = ({ children }: { children?: React.ReactNode }) => ( + {children} + ); + const { result } = renderHook(() => useLink_unstable({ inline: false }, null), { wrapper }); + + expect(result.current.inline).toBe(false); + }); +}); diff --git a/packages/react-components/react-link/library/src/components/Link/useLink.ts b/packages/react-components/react-link/library/src/components/Link/useLink.ts index 03c5e2b72264d..aa96de0ec1b3e 100644 --- a/packages/react-components/react-link/library/src/components/Link/useLink.ts +++ b/packages/react-components/react-link/library/src/components/Link/useLink.ts @@ -17,20 +17,22 @@ export const useLink_unstable = ( ref: React.Ref, ): LinkState => { const backgroundAppearance = useBackgroundAppearance(); - const { appearance = 'default', ...baseProps } = props; + const { inline: inlineContext } = useLinkContext(); + const { appearance = 'default', inline: inlineProp, ...baseProps } = props; const state = useLinkBase_unstable(baseProps, ref); return { appearance, backgroundAppearance, + inline: inlineProp ?? !!inlineContext, ...state, }; }; /** * Base hook for Link component, which manages state related to ARIA, keyboard handling, - * disabled behavior, and slot structure. This hook excludes design-specific props (appearance). + * disabled behavior, and slot structure. This hook excludes design-specific props (appearance, inline). * * @param props - User provided props to the Link component. * @param ref - User provided ref to be passed to the Link component. @@ -39,8 +41,7 @@ export const useLinkBase_unstable = ( props: LinkBaseProps, ref: React.Ref, ): LinkBaseState => { - const { inline: inlineContext } = useLinkContext(); - const { disabled = false, disabledFocusable = false, inline = false } = props; + const { disabled = false, disabledFocusable = false } = props; const elementType = props.as || (props.href ? 'a' : 'button'); @@ -56,7 +57,6 @@ export const useLinkBase_unstable = ( // Props passed at the top-level disabled, disabledFocusable, - inline: inline ?? !!inlineContext, // Slots definition components: {