diff --git a/apps/vr-tests-react-components/src/stories/Field.stories.tsx b/apps/vr-tests-react-components/src/stories/Field.stories.tsx
index 4f1875277be0f6..9fe27cb2ad1a76 100644
--- a/apps/vr-tests-react-components/src/stories/Field.stories.tsx
+++ b/apps/vr-tests-react-components/src/stories/Field.stories.tsx
@@ -4,6 +4,7 @@ import { Checkbox } from '@fluentui/react-checkbox';
import { Combobox, Dropdown } from '@fluentui/react-combobox';
import { Field } from '@fluentui/react-field';
import { Dismiss12Filled } from '@fluentui/react-icons';
+import { InfoButton } from '@fluentui/react-infobutton';
import { Input } from '@fluentui/react-input';
import { ProgressBar } from '@fluentui/react-progress';
import { Radio, RadioGroup } from '@fluentui/react-radio';
@@ -109,6 +110,44 @@ storiesOf('Field', module)
))
+ .addStory('infoButton', () => (
+ }>
+
+
+ ))
+ .addStory('infoButton+required', () => (
+ }>
+
+
+ ))
+ .addStory('infoButton+longLabel', () => (
+ }
+ >
+
+
+ ))
+ .addStory('infoButton+size:small', () => (
+ } size="small">
+
+
+ ))
+ .addStory('infoButton+size:large', () => (
+ } size="large">
+
+
+ ))
+ .addStory('infoButton+noLabel', () => (
+ }>
+
+
+ ))
+ .addStory('infoButton+horizontal', () => (
+ }>
+
+
+ ))
.addStory('Checkbox:error', () => (
diff --git a/change/@fluentui-react-components-99dc4185-3c25-4a6f-81e1-81adc234a3c8.json b/change/@fluentui-react-components-99dc4185-3c25-4a6f-81e1-81adc234a3c8.json
new file mode 100644
index 00000000000000..945cd6282f9059
--- /dev/null
+++ b/change/@fluentui-react-components-99dc4185-3c25-4a6f-81e1-81adc234a3c8.json
@@ -0,0 +1,7 @@
+{
+ "type": "patch",
+ "comment": "chore: Export new context hooks for InfoButton and Field",
+ "packageName": "@fluentui/react-components",
+ "email": "behowell@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/change/@fluentui-react-field-a0bd4a40-820a-43d0-8dfd-321fdfc296e7.json b/change/@fluentui-react-field-a0bd4a40-820a-43d0-8dfd-321fdfc296e7.json
new file mode 100644
index 00000000000000..ade3cff3219802
--- /dev/null
+++ b/change/@fluentui-react-field-a0bd4a40-820a-43d0-8dfd-321fdfc296e7.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "feat: Add infoButton slot to Field",
+ "packageName": "@fluentui/react-field",
+ "email": "behowell@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/change/@fluentui-react-infobutton-65695e5c-b22d-4b34-98a1-8c2dd217b38a.json b/change/@fluentui-react-infobutton-65695e5c-b22d-4b34-98a1-8c2dd217b38a.json
new file mode 100644
index 00000000000000..2463c60d02a353
--- /dev/null
+++ b/change/@fluentui-react-infobutton-65695e5c-b22d-4b34-98a1-8c2dd217b38a.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "feat: Add InfoButtonContext to allow other components to set default prop values",
+ "packageName": "@fluentui/react-infobutton",
+ "email": "behowell@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/packages/react-components/react-components/etc/react-components.unstable.api.md b/packages/react-components/react-components/etc/react-components.unstable.api.md
index 3e581bc96d5492..614b7b46f95821 100644
--- a/packages/react-components/react-components/etc/react-components.unstable.api.md
+++ b/packages/react-components/react-components/etc/react-components.unstable.api.md
@@ -44,6 +44,8 @@ import { FieldSlots } from '@fluentui/react-field';
import { FieldState } from '@fluentui/react-field';
import { InfoButton } from '@fluentui/react-infobutton';
import { infoButtonClassNames } from '@fluentui/react-infobutton';
+import { InfoButtonContextProvider } from '@fluentui/react-infobutton';
+import { InfoButtonContextValue } from '@fluentui/react-infobutton';
import { InfoButtonProps } from '@fluentui/react-infobutton';
import { InfoButtonSlots } from '@fluentui/react-infobutton';
import { InfoButtonState } from '@fluentui/react-infobutton';
@@ -90,8 +92,10 @@ import { useCardPreview_unstable } from '@fluentui/react-card';
import { useCardPreviewStyles_unstable } from '@fluentui/react-card';
import { useCardStyles_unstable } from '@fluentui/react-card';
import { useField_unstable } from '@fluentui/react-field';
+import { useFieldContextValues_unstable } from '@fluentui/react-field';
import { useFieldStyles_unstable } from '@fluentui/react-field';
import { useInfoButton_unstable } from '@fluentui/react-infobutton';
+import { useInfoButtonContext } from '@fluentui/react-infobutton';
import { useInfoButtonStyles_unstable } from '@fluentui/react-infobutton';
import { useIntersectionObserver } from '@fluentui/react-virtualizer';
import { useVirtualizer_unstable } from '@fluentui/react-virtualizer';
@@ -183,6 +187,10 @@ export { InfoButton }
export { infoButtonClassNames }
+export { InfoButtonContextProvider }
+
+export { InfoButtonContextValue }
+
export { InfoButtonProps }
export { InfoButtonSlots }
@@ -275,10 +283,14 @@ export { useCardStyles_unstable }
export { useField_unstable }
+export { useFieldContextValues_unstable }
+
export { useFieldStyles_unstable }
export { useInfoButton_unstable }
+export { useInfoButtonContext }
+
export { useInfoButtonStyles_unstable }
export { useIntersectionObserver }
diff --git a/packages/react-components/react-components/src/unstable/index.ts b/packages/react-components/react-components/src/unstable/index.ts
index d1f0f1f2a32bb7..e2890b7c7e9884 100644
--- a/packages/react-components/react-components/src/unstable/index.ts
+++ b/packages/react-components/react-components/src/unstable/index.ts
@@ -50,11 +50,18 @@ export type {
export {
InfoButton,
infoButtonClassNames,
+ InfoButtonContextProvider,
useInfoButton_unstable,
+ useInfoButtonContext,
useInfoButtonStyles_unstable,
renderInfoButton_unstable,
} from '@fluentui/react-infobutton';
-export type { InfoButtonProps, InfoButtonSlots, InfoButtonState } from '@fluentui/react-infobutton';
+export type {
+ InfoButtonContextValue,
+ InfoButtonProps,
+ InfoButtonSlots,
+ InfoButtonState,
+} from '@fluentui/react-infobutton';
// eslint-disable-next-line deprecation/deprecation
export { CheckboxField_unstable as CheckboxField, checkboxFieldClassNames } from '@fluentui/react-checkbox';
@@ -110,8 +117,9 @@ export {
Field,
fieldClassNames,
renderField_unstable,
- useFieldStyles_unstable,
useField_unstable,
+ useFieldContextValues_unstable,
+ useFieldStyles_unstable,
} from '@fluentui/react-field';
export type { FieldProps, FieldSlots, FieldState } from '@fluentui/react-field';
diff --git a/packages/react-components/react-field/etc/react-field.api.md b/packages/react-components/react-field/etc/react-field.api.md
index 3aef86de247097..5e244b9441a629 100644
--- a/packages/react-components/react-field/etc/react-field.api.md
+++ b/packages/react-components/react-field/etc/react-field.api.md
@@ -9,6 +9,7 @@
import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import { ForwardRefComponent } from '@fluentui/react-utilities';
+import type { InfoButtonContextValue } from '@fluentui/react-infobutton';
import { Label } from '@fluentui/react-label';
import * as React_2 from 'react';
import type { Slot } from '@fluentui/react-utilities';
@@ -39,13 +40,15 @@ export type FieldProps = Omit, 'children'> & {
export type FieldSlots = {
root: NonNullable>;
label?: Slot;
+ infoButton?: Slot<'span'>;
+ labelWrapper?: Slot<'div'>;
validationMessage?: Slot<'div'>;
validationMessageIcon?: Slot<'span'>;
hint?: Slot<'div'>;
};
// @public
-export type FieldState = ComponentState> & Required>;
+export type FieldState = ComponentState> & Required>;
// @internal @deprecated (undocumented)
export const getDeprecatedFieldClassNames: (controlRootClassName: string) => {
@@ -64,11 +67,14 @@ export function makeDeprecatedField(Control: React_2.ComponentType
}): ForwardRefComponent>;
// @public
-export const renderField_unstable: (state: FieldState) => JSX.Element;
+export const renderField_unstable: (state: FieldState, contextValues?: FieldContextValues | undefined) => JSX.Element;
// @public
export const useField_unstable: (props: FieldProps, ref: React_2.Ref) => FieldState;
+// @public (undocumented)
+export const useFieldContextValues_unstable: (state: FieldState) => FieldContextValues;
+
// @public
export const useFieldStyles_unstable: (state: FieldState) => void;
diff --git a/packages/react-components/react-field/package.json b/packages/react-components/react-field/package.json
index ba48d7703b9f8e..74ad9d04fd3e00 100644
--- a/packages/react-components/react-field/package.json
+++ b/packages/react-components/react-field/package.json
@@ -34,6 +34,7 @@
"dependencies": {
"@fluentui/react-context-selector": "^9.1.10",
"@fluentui/react-icons": "^2.0.175",
+ "@fluentui/react-infobutton": "9.0.0-beta.17",
"@fluentui/react-label": "^9.0.22",
"@fluentui/react-theme": "^9.1.5",
"@fluentui/react-utilities": "^9.6.0",
diff --git a/packages/react-components/react-field/src/components/Field/Field.test.tsx b/packages/react-components/react-field/src/components/Field/Field.test.tsx
index 175093bb25f35d..e86e2b8aae3b39 100644
--- a/packages/react-components/react-field/src/components/Field/Field.test.tsx
+++ b/packages/react-components/react-field/src/components/Field/Field.test.tsx
@@ -1,4 +1,5 @@
import * as React from 'react';
+import { InfoButton } from '@fluentui/react-infobutton';
import { render } from '@testing-library/react';
import { isConformant } from '../../testing/isConformant';
import { Field } from './index';
@@ -12,9 +13,9 @@ describe('Field', () => {
{
props: {
label: 'Test label',
+ infoButton: ,
hint: 'Test hint',
validationMessage: 'Test validation message',
- validationState: 'error',
},
},
],
@@ -189,4 +190,18 @@ describe('Field', () => {
'aria-required': true,
});
});
+
+ it('passes context to InfoButton to allow it to be labelledby the Field label', () => {
+ const result = render(
+ }>
+
+ ,
+ );
+
+ const label = result.getByText('Test label');
+ const infoButton = result.getByRole('button');
+
+ expect(label.id).toBeTruthy();
+ expect(infoButton.getAttribute('aria-labelledby')).toBe(`${label.id} ${infoButton.id}`);
+ });
});
diff --git a/packages/react-components/react-field/src/components/Field/Field.tsx b/packages/react-components/react-field/src/components/Field/Field.tsx
index d498bebccb35f2..ca6575fe7fb4b4 100644
--- a/packages/react-components/react-field/src/components/Field/Field.tsx
+++ b/packages/react-components/react-field/src/components/Field/Field.tsx
@@ -1,5 +1,6 @@
import * as React from 'react';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
+import { useFieldContextValues_unstable } from '../../contexts/useFieldContextValues';
import type { FieldProps } from './Field.types';
import { renderField_unstable } from './renderField';
import { useField_unstable } from './useField';
@@ -7,8 +8,9 @@ import { useFieldStyles_unstable } from './useFieldStyles';
export const Field: ForwardRefComponent = React.forwardRef((props, ref) => {
const state = useField_unstable(props, ref);
+ const contextValues = useFieldContextValues_unstable(state);
useFieldStyles_unstable(state);
- return renderField_unstable(state);
+ return renderField_unstable(state, contextValues);
});
Field.displayName = 'Field';
diff --git a/packages/react-components/react-field/src/components/Field/Field.types.ts b/packages/react-components/react-field/src/components/Field/Field.types.ts
index 1f355046bf53fc..b5061ab7407840 100644
--- a/packages/react-components/react-field/src/components/Field/Field.types.ts
+++ b/packages/react-components/react-field/src/components/Field/Field.types.ts
@@ -1,4 +1,5 @@
import * as React from 'react';
+import type { InfoButtonContextValue } from '@fluentui/react-infobutton';
import { Label } from '@fluentui/react-label';
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
@@ -21,6 +22,23 @@ export type FieldSlots = {
*/
label?: Slot;
+ /**
+ * Slot to hold an InfoButton associated with the field's label.
+ *
+ * If supplied, it should be a single `` control.
+ *
+ * @example
+ * ```
+ * } />
+ * ```
+ */
+ infoButton?: Slot<'span'>;
+
+ /**
+ * Wrapper around the label and infoButton. Only rendered when there is an infoButton.
+ */
+ labelWrapper?: Slot<'div'>;
+
/**
* A message about the validation state. By default, this is an error message, but it can be a success, warning,
* or custom message by setting `validationState`.
@@ -100,4 +118,8 @@ export type FieldProps = Omit, 'children'> & {
* State used in rendering Field
*/
export type FieldState = ComponentState> &
- Required>;
+ Required>;
+
+export type FieldContextValues = {
+ infoButton: InfoButtonContextValue;
+};
diff --git a/packages/react-components/react-field/src/components/Field/renderField.tsx b/packages/react-components/react-field/src/components/Field/renderField.tsx
index 309226f59c71b9..fc4e78efcbb2f2 100644
--- a/packages/react-components/react-field/src/components/Field/renderField.tsx
+++ b/packages/react-components/react-field/src/components/Field/renderField.tsx
@@ -1,16 +1,28 @@
import * as React from 'react';
+import { InfoButtonContextProvider } from '@fluentui/react-infobutton';
import { getSlots } from '@fluentui/react-utilities';
-import type { FieldSlots, FieldState } from './Field.types';
+import type { FieldContextValues, FieldSlots, FieldState } from './Field.types';
/**
* Render the final JSX of Field
*/
-export const renderField_unstable = (state: FieldState) => {
+export const renderField_unstable = (state: FieldState, contextValues?: FieldContextValues) => {
const { slots, slotProps } = getSlots(state);
return (
- {slots.label && }
+ {slots.labelWrapper ? (
+
+ {slots.label && }
+ {slots.infoButton && (
+
+
+
+ )}
+
+ ) : (
+ slots.label &&
+ )}
{slotProps.root.children}
{slots.validationMessage && (
diff --git a/packages/react-components/react-field/src/components/Field/useField.tsx b/packages/react-components/react-field/src/components/Field/useField.tsx
index 172b4bfd7f703d..0f33d5253600ac 100644
--- a/packages/react-components/react-field/src/components/Field/useField.tsx
+++ b/packages/react-components/react-field/src/components/Field/useField.tsx
@@ -27,7 +27,7 @@ export const useField_unstable = (props: FieldProps, ref: React.Ref = {
root: `fui-Field`,
+ labelWrapper: `fui-Field__labelWrapper`,
label: `fui-Field__label`,
+ infoButton: `fui-Field__infoButton`,
validationMessage: `fui-Field__validationMessage`,
validationMessageIcon: `fui-Field__validationMessageIcon`,
hint: `fui-Field__hint`,
@@ -37,13 +39,15 @@ const useRootStyles = makeStyles({
},
});
-const useLabelStyles = makeStyles({
+const useLabelWrapperStyles = makeStyles({
base: {
+ // This padding must match the negative margin on infoButton
paddingTop: tokens.spacingVerticalXXS,
paddingBottom: tokens.spacingVerticalXXS,
},
large: {
+ // This padding must match the negative margin on infoButton
paddingTop: '1px',
paddingBottom: '1px',
},
@@ -63,6 +67,29 @@ const useLabelStyles = makeStyles({
},
});
+const useLabelStyles = makeStyles({
+ base: {
+ verticalAlign: 'top',
+ },
+});
+
+const useInfoButtonStyles = makeStyles({
+ base: {
+ display: 'inline-block',
+ verticalAlign: 'top',
+
+ // Negative margin to offset the labelWrapper's padding
+ marginTop: `calc(0px - ${tokens.spacingVerticalXXS})`,
+ marginBottom: `calc(0px - ${tokens.spacingVerticalXXS})`,
+ },
+
+ large: {
+ // Negative margin to offset the labelWrapper's padding
+ marginTop: '-1px',
+ marginBottom: '-1px',
+ },
+});
+
const useSecondaryTextBaseClassName = makeResetStyles({
marginTop: tokens.spacingVerticalXXS,
color: tokens.colorNeutralForeground3,
@@ -120,19 +147,45 @@ export const useFieldStyles_unstable = (state: FieldState) => {
state.root.className,
);
+ const labelWrapperStyles = useLabelWrapperStyles();
+
+ // Class name applied to the labelWrapper if it is present, or the label itself otherwise.
+ const labelWrapperClassName = mergeClasses(
+ labelWrapperStyles.base,
+ horizontal && labelWrapperStyles.horizontal,
+ !horizontal && labelWrapperStyles.vertical,
+ state.size === 'large' && labelWrapperStyles.large,
+ !horizontal && state.size === 'large' && labelWrapperStyles.verticalLarge,
+ );
+
+ if (state.labelWrapper) {
+ state.labelWrapper.className = mergeClasses(
+ fieldClassNames.labelWrapper,
+ labelWrapperClassName,
+ state.labelWrapper.className,
+ );
+ }
+
const labelStyles = useLabelStyles();
if (state.label) {
state.label.className = mergeClasses(
fieldClassNames.label,
labelStyles.base,
- horizontal && labelStyles.horizontal,
- !horizontal && labelStyles.vertical,
- state.label.size === 'large' && labelStyles.large,
- !horizontal && state.label.size === 'large' && labelStyles.verticalLarge,
+ !state.labelWrapper && labelWrapperClassName,
state.label.className,
);
}
+ const infoButtonStyles = useInfoButtonStyles();
+ if (state.infoButton) {
+ state.infoButton.className = mergeClasses(
+ fieldClassNames.infoButton,
+ infoButtonStyles.base,
+ state.size === 'large' && infoButtonStyles.large,
+ state.infoButton.className,
+ );
+ }
+
const validationMessageIconBaseClassName = useValidationMessageIconBaseClassName();
const validationMessageIconStyles = useValidationMessageIconStyles();
if (state.validationMessageIcon) {
diff --git a/packages/react-components/react-field/src/contexts/index.ts b/packages/react-components/react-field/src/contexts/index.ts
new file mode 100644
index 00000000000000..f2d6ec3483efd5
--- /dev/null
+++ b/packages/react-components/react-field/src/contexts/index.ts
@@ -0,0 +1 @@
+export * from './useFieldContextValues';
diff --git a/packages/react-components/react-field/src/contexts/useFieldContextValues.ts b/packages/react-components/react-field/src/contexts/useFieldContextValues.ts
new file mode 100644
index 00000000000000..e7ab09d16cabc3
--- /dev/null
+++ b/packages/react-components/react-field/src/contexts/useFieldContextValues.ts
@@ -0,0 +1,19 @@
+import * as React from 'react';
+
+import { InfoButtonContextValue } from '@fluentui/react-infobutton/src/index';
+import type { FieldContextValues, FieldState } from '../Field';
+
+export const useFieldContextValues_unstable = (state: FieldState): FieldContextValues => {
+ const { size, label } = state;
+ const associatedLabelId = label?.id;
+
+ const infoButton: InfoButtonContextValue = React.useMemo(
+ () => ({
+ associatedLabelId,
+ size,
+ }),
+ [associatedLabelId, size],
+ );
+
+ return { infoButton };
+};
diff --git a/packages/react-components/react-field/src/index.ts b/packages/react-components/react-field/src/index.ts
index 75d8898bcfac15..0ea41b81a4196d 100644
--- a/packages/react-components/react-field/src/index.ts
+++ b/packages/react-components/react-field/src/index.ts
@@ -1,6 +1,8 @@
export { Field, fieldClassNames, renderField_unstable, useFieldStyles_unstable, useField_unstable } from './Field';
export type { FieldProps, FieldSlots, FieldState } from './Field';
+export { useFieldContextValues_unstable } from './contexts/useFieldContextValues';
+
// eslint-disable-next-line deprecation/deprecation
export { getDeprecatedFieldClassNames, makeDeprecatedField } from './util/makeDeprecatedField';
// eslint-disable-next-line deprecation/deprecation
diff --git a/packages/react-components/react-field/src/util/makeDeprecatedField.tsx b/packages/react-components/react-field/src/util/makeDeprecatedField.tsx
index 14c870e335de26..49d59eae507cef 100644
--- a/packages/react-components/react-field/src/util/makeDeprecatedField.tsx
+++ b/packages/react-components/react-field/src/util/makeDeprecatedField.tsx
@@ -2,7 +2,7 @@
import * as React from 'react';
import { ForwardRefComponent } from '@fluentui/react-utilities';
import type { FieldProps } from '../Field';
-import { Field, fieldClassNames } from '../Field';
+import { Field } from '../Field';
/**
* @deprecated Only for use to make deprecated [Control]Field shim components.
@@ -101,6 +101,10 @@ export function makeDeprecatedField(
* @internal
*/
export const getDeprecatedFieldClassNames = (controlRootClassName: string) => ({
- ...fieldClassNames,
control: controlRootClassName,
+ root: `fui-Field`,
+ label: `fui-Field__label`,
+ validationMessage: `fui-Field__validationMessage`,
+ validationMessageIcon: `fui-Field__validationMessageIcon`,
+ hint: `fui-Field__hint`,
});
diff --git a/packages/react-components/react-field/stories/Field/FieldWithInfoButton.stories.tsx b/packages/react-components/react-field/stories/Field/FieldWithInfoButton.stories.tsx
new file mode 100644
index 00000000000000..43951b2dd60700
--- /dev/null
+++ b/packages/react-components/react-field/stories/Field/FieldWithInfoButton.stories.tsx
@@ -0,0 +1,19 @@
+import * as React from 'react';
+
+import { Input } from '@fluentui/react-components';
+import { Field, InfoButton } from '@fluentui/react-components/unstable';
+
+export const WithInfoButton = () => (
+ }>
+
+
+);
+
+WithInfoButton.storyName = 'With InfoButton';
+WithInfoButton.parameters = {
+ docs: {
+ description: {
+ story: 'The `infoButton` slot allows the addition of an `` after the label.',
+ },
+ },
+};
diff --git a/packages/react-components/react-field/stories/Field/index.stories.tsx b/packages/react-components/react-field/stories/Field/index.stories.tsx
index dda5953c7d5fea..d0f3e1572f0a7d 100644
--- a/packages/react-components/react-field/stories/Field/index.stories.tsx
+++ b/packages/react-components/react-field/stories/Field/index.stories.tsx
@@ -8,6 +8,7 @@ export { Hint } from './FieldHint.stories';
export { Horizontal } from './FieldHorizontal.stories';
export { Required } from './FieldRequired.stories';
export { Size } from './FieldSize.stories';
+export { WithInfoButton } from './FieldWithInfoButton.stories';
export { ComponentExamples } from './FieldComponentExamples.stories';
export { RenderFunction } from './FieldRenderFunction.stories';
diff --git a/packages/react-components/react-infobutton/etc/react-infobutton.api.md b/packages/react-components/react-infobutton/etc/react-infobutton.api.md
index 5a0ebabff8ef9f..ca2f42ce474d59 100644
--- a/packages/react-components/react-infobutton/etc/react-infobutton.api.md
+++ b/packages/react-components/react-infobutton/etc/react-infobutton.api.md
@@ -21,8 +21,15 @@ export const InfoButton: ForwardRefComponent;
// @public (undocumented)
export const infoButtonClassNames: SlotClassNames;
+// @internal (undocumented)
+export const InfoButtonContextProvider: React_2.Provider;
+
+// @internal (undocumented)
+export type InfoButtonContextValue = Pick;
+
// @public
export type InfoButtonProps = Omit>, 'disabled'> & {
+ associatedLabelId?: string;
size?: 'small' | 'medium' | 'large';
};
@@ -42,6 +49,9 @@ export const renderInfoButton_unstable: (state: InfoButtonState) => JSX.Element;
// @public
export const useInfoButton_unstable: (props: InfoButtonProps, ref: React_2.Ref) => InfoButtonState;
+// @internal (undocumented)
+export const useInfoButtonContext: () => InfoButtonContextValue;
+
// @public
export const useInfoButtonStyles_unstable: (state: InfoButtonState) => InfoButtonState;
diff --git a/packages/react-components/react-infobutton/src/components/InfoButton/InfoButton.types.ts b/packages/react-components/react-infobutton/src/components/InfoButton/InfoButton.types.ts
index 12e34ee9987322..921f7d846babd8 100644
--- a/packages/react-components/react-infobutton/src/components/InfoButton/InfoButton.types.ts
+++ b/packages/react-components/react-infobutton/src/components/InfoButton/InfoButton.types.ts
@@ -19,6 +19,11 @@ export type InfoButtonSlots = {
* InfoButton Props
*/
export type InfoButtonProps = Omit>, 'disabled'> & {
+ /**
+ * The ID of the control label associated with this InfoButton. Used to set the info button's aria-labelledby.
+ */
+ associatedLabelId?: string;
+
/**
* Size of the InfoButton.
*
diff --git a/packages/react-components/react-infobutton/src/components/InfoButton/useInfoButton.tsx b/packages/react-components/react-infobutton/src/components/InfoButton/useInfoButton.tsx
index b695ca5fea4472..cce2b95342c920 100644
--- a/packages/react-components/react-infobutton/src/components/InfoButton/useInfoButton.tsx
+++ b/packages/react-components/react-infobutton/src/components/InfoButton/useInfoButton.tsx
@@ -1,10 +1,17 @@
import * as React from 'react';
-import { DefaultInfoButtonIcon12, DefaultInfoButtonIcon16, DefaultInfoButtonIcon20 } from './DefaultInfoButtonIcons';
-import { getNativeElementProps, mergeCallbacks, resolveShorthand } from '@fluentui/react-utilities';
+
+import type { PopoverProps } from '@fluentui/react-popover';
import { Popover, PopoverSurface } from '@fluentui/react-popover';
-import { useControllableState } from '@fluentui/react-utilities';
+import {
+ getNativeElementProps,
+ mergeCallbacks,
+ resolveShorthand,
+ useControllableState,
+ useId,
+} from '@fluentui/react-utilities';
+import { useInfoButtonContext } from '../../contexts/InfoButtonContext';
+import { DefaultInfoButtonIcon12, DefaultInfoButtonIcon16, DefaultInfoButtonIcon20 } from './DefaultInfoButtonIcons';
import type { InfoButtonProps, InfoButtonState } from './InfoButton.types';
-import type { PopoverProps } from '@fluentui/react-popover';
const infoButtonIconMap = {
small: ,
@@ -28,7 +35,8 @@ const popoverSizeMap = {
* @param ref - reference to root HTMLElement of InfoButton
*/
export const useInfoButton_unstable = (props: InfoButtonProps, ref: React.Ref): InfoButtonState => {
- const { size = 'medium' } = props;
+ const context = useInfoButtonContext();
+ const { associatedLabelId = context.associatedLabelId, size = context.size || 'medium' } = props;
const state: InfoButtonState = {
size,
@@ -42,6 +50,7 @@ export const useInfoButton_unstable = (props: InfoButtonProps, ref: React.Ref(undefined);
+
+/**
+ * @internal
+ */
+export type InfoButtonContextValue = Pick;
+
+const infoButtonContextDefaultValue: InfoButtonContextValue = {};
+
+/**
+ * @internal
+ */
+export const InfoButtonContextProvider = infoButtonContext.Provider;
+
+/**
+ * @internal
+ */
+export const useInfoButtonContext = () => React.useContext(infoButtonContext) ?? infoButtonContextDefaultValue;
diff --git a/packages/react-components/react-infobutton/src/contexts/index.ts b/packages/react-components/react-infobutton/src/contexts/index.ts
new file mode 100644
index 00000000000000..42e93b265566e3
--- /dev/null
+++ b/packages/react-components/react-infobutton/src/contexts/index.ts
@@ -0,0 +1 @@
+export * from './InfoButtonContext';
diff --git a/packages/react-components/react-infobutton/src/index.ts b/packages/react-components/react-infobutton/src/index.ts
index 94f120cfd96a84..630620a1dca77e 100644
--- a/packages/react-components/react-infobutton/src/index.ts
+++ b/packages/react-components/react-infobutton/src/index.ts
@@ -6,3 +6,6 @@ export {
useInfoButton_unstable,
} from './InfoButton';
export type { InfoButtonProps, InfoButtonSlots, InfoButtonState } from './InfoButton';
+
+export { InfoButtonContextProvider, useInfoButtonContext } from './contexts/index';
+export type { InfoButtonContextValue } from './contexts/index';