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';