diff --git a/apps/vr-tests-react-components/src/stories/InfoLabel.stories.tsx b/apps/vr-tests-react-components/src/stories/InfoLabel.stories.tsx new file mode 100644 index 00000000000000..5d8a4b85c29712 --- /dev/null +++ b/apps/vr-tests-react-components/src/stories/InfoLabel.stories.tsx @@ -0,0 +1,43 @@ +import * as React from 'react'; + +import { Steps, StoryWright } from 'storywright'; +import { InfoLabel } from '@fluentui/react-infobutton'; +import { storiesOf } from '@storybook/react'; +import { TestWrapperDecoratorFixedWidth } from '../utilities/TestWrapperDecorator'; + +storiesOf('InfoLabel', module) + .addDecorator(TestWrapperDecoratorFixedWidth) + .addDecorator(story => ( + {story()} + )) + .addStory('default', () => This is an info label, { + includeHighContrast: true, + includeDarkMode: true, + includeRtl: true, + }) + .addStory('wrap', () => ( + + This is a very long info label that should wrap to multiple lines and put the info button on the last line + + )) + .addStory('size:small', () => ( + + This is a small info label + + )) + .addStory('size:large', () => ( + + This is a large info label + + )) + .addStory( + 'required', + () => ( + + This is a required info label + + ), + { + includeRtl: true, + }, + ); diff --git a/change/@fluentui-react-components-fff93a07-f531-47a4-ac89-03ebd885c5dc.json b/change/@fluentui-react-components-fff93a07-f531-47a4-ac89-03ebd885c5dc.json new file mode 100644 index 00000000000000..a0352d769d0e92 --- /dev/null +++ b/change/@fluentui-react-components-fff93a07-f531-47a4-ac89-03ebd885c5dc.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: Export InfoLabel from react-components/unstable", + "packageName": "@fluentui/react-components", + "email": "behowell@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-infobutton-31dcdbe1-fdb8-4865-b821-6717fd7a1db1.json b/change/@fluentui-react-infobutton-31dcdbe1-fdb8-4865-b821-6717fd7a1db1.json new file mode 100644 index 00000000000000..0a672bb9ada1d9 --- /dev/null +++ b/change/@fluentui-react-infobutton-31dcdbe1-fdb8-4865-b821-6717fd7a1db1.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "feat: Add InfoLabel component", + "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 69e11834e36faf..536698590ea8a5 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 @@ -30,6 +30,11 @@ import { infoButtonClassNames } from '@fluentui/react-infobutton'; import { InfoButtonProps } from '@fluentui/react-infobutton'; import { InfoButtonSlots } from '@fluentui/react-infobutton'; import { InfoButtonState } from '@fluentui/react-infobutton'; +import { InfoLabel } from '@fluentui/react-infobutton'; +import { infoLabelClassNames } from '@fluentui/react-infobutton'; +import { InfoLabelProps } from '@fluentui/react-infobutton'; +import { InfoLabelSlots } from '@fluentui/react-infobutton'; +import { InfoLabelState } from '@fluentui/react-infobutton'; import { InputField_unstable as InputField } from '@fluentui/react-input'; import { inputFieldClassNames } from '@fluentui/react-input'; import { InputFieldProps_unstable as InputFieldProps } from '@fluentui/react-input'; @@ -43,6 +48,7 @@ import { RadioGroupFieldProps_unstable as RadioGroupFieldProps } from '@fluentui import { renderAlert_unstable } from '@fluentui/react-alert'; import { renderField_unstable } from '@fluentui/react-field'; import { renderInfoButton_unstable } from '@fluentui/react-infobutton'; +import { renderInfoLabel_unstable } from '@fluentui/react-infobutton'; import { renderSkeleton_unstable } from '@fluentui/react-skeleton'; import { renderSkeletonItem_unstable } from '@fluentui/react-skeleton'; import { renderTree_unstable } from '@fluentui/react-tree'; @@ -113,6 +119,8 @@ import { useFieldStyles_unstable } from '@fluentui/react-field'; import { useFlatTree_unstable } from '@fluentui/react-tree'; import { useInfoButton_unstable } from '@fluentui/react-infobutton'; import { useInfoButtonStyles_unstable } from '@fluentui/react-infobutton'; +import { useInfoLabel_unstable } from '@fluentui/react-infobutton'; +import { useInfoLabelStyles_unstable } from '@fluentui/react-infobutton'; import { useIntersectionObserver } from '@fluentui/react-virtualizer'; import { useSkeleton_unstable } from '@fluentui/react-skeleton'; import { useSkeletonContext } from '@fluentui/react-skeleton'; @@ -190,6 +198,16 @@ export { InfoButtonSlots } export { InfoButtonState } +export { InfoLabel } + +export { infoLabelClassNames } + +export { InfoLabelProps } + +export { InfoLabelSlots } + +export { InfoLabelState } + export { InputField } export { inputFieldClassNames } @@ -216,6 +234,8 @@ export { renderField_unstable } export { renderInfoButton_unstable } +export { renderInfoLabel_unstable } + export { renderSkeleton_unstable } export { renderSkeletonItem_unstable } @@ -356,6 +376,10 @@ export { useInfoButton_unstable } export { useInfoButtonStyles_unstable } +export { useInfoLabel_unstable } + +export { useInfoLabelStyles_unstable } + export { useIntersectionObserver } export { useSkeleton_unstable } diff --git a/packages/react-components/react-components/src/unstable/index.ts b/packages/react-components/react-components/src/unstable/index.ts index d79893da6a36f3..60eb1ef64ef74f 100644 --- a/packages/react-components/react-components/src/unstable/index.ts +++ b/packages/react-components/react-components/src/unstable/index.ts @@ -15,8 +15,20 @@ export { useInfoButton_unstable, useInfoButtonStyles_unstable, renderInfoButton_unstable, + InfoLabel, + infoLabelClassNames, + renderInfoLabel_unstable, + useInfoLabel_unstable, + useInfoLabelStyles_unstable, +} from '@fluentui/react-infobutton'; +export type { + InfoButtonProps, + InfoButtonSlots, + InfoButtonState, + InfoLabelProps, + InfoLabelSlots, + InfoLabelState, } from '@fluentui/react-infobutton'; -export type { InfoButtonProps, InfoButtonSlots, InfoButtonState } from '@fluentui/react-infobutton'; // eslint-disable-next-line deprecation/deprecation export { CheckboxField_unstable as CheckboxField, checkboxFieldClassNames } from '@fluentui/react-checkbox'; diff --git a/packages/react-components/react-infobutton/bundle-size/InfoLabel.fixture.js b/packages/react-components/react-infobutton/bundle-size/InfoLabel.fixture.js new file mode 100644 index 00000000000000..db396f5802c382 --- /dev/null +++ b/packages/react-components/react-infobutton/bundle-size/InfoLabel.fixture.js @@ -0,0 +1,7 @@ +import { InfoLabel } from '@fluentui/react-infobutton'; + +console.log(InfoLabel); + +export default { + name: 'InfoLabel', +}; 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..1d20b44a132451 100644 --- a/packages/react-components/react-infobutton/etc/react-infobutton.api.md +++ b/packages/react-components/react-infobutton/etc/react-infobutton.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 { Label } from '@fluentui/react-label'; import type { PopoverProps } from '@fluentui/react-popover'; import type { PopoverSurface } from '@fluentui/react-popover'; import * as React_2 from 'react'; @@ -36,15 +37,45 @@ export type InfoButtonSlots = { // @public export type InfoButtonState = ComponentState & Required>; +// @public +export const InfoLabel: ForwardRefComponent; + +// @public (undocumented) +export const infoLabelClassNames: SlotClassNames; + +// @public +export type InfoLabelProps = ComponentProps, 'label'> & { + info?: InfoButtonProps['content']; +}; + +// @public (undocumented) +export type InfoLabelSlots = { + root: NonNullable>; + label: NonNullable>; + infoButton: Slot; +}; + +// @public +export type InfoLabelState = ComponentState & Pick; + // @public export const renderInfoButton_unstable: (state: InfoButtonState) => JSX.Element; +// @public +export const renderInfoLabel_unstable: (state: InfoLabelState) => JSX.Element; + // @public export const useInfoButton_unstable: (props: InfoButtonProps, ref: React_2.Ref) => InfoButtonState; // @public export const useInfoButtonStyles_unstable: (state: InfoButtonState) => InfoButtonState; +// @public +export const useInfoLabel_unstable: (props: InfoLabelProps, ref: React_2.Ref) => InfoLabelState; + +// @public +export const useInfoLabelStyles_unstable: (state: InfoLabelState) => InfoLabelState; + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/react-components/react-infobutton/package.json b/packages/react-components/react-infobutton/package.json index 3958451f4f1020..2c767b8e7cd7c4 100644 --- a/packages/react-components/react-infobutton/package.json +++ b/packages/react-components/react-infobutton/package.json @@ -33,6 +33,7 @@ }, "dependencies": { "@fluentui/react-icons": "^2.0.175", + "@fluentui/react-label": "^9.1.3", "@fluentui/react-popover": "^9.5.3", "@fluentui/react-tabster": "^9.5.7", "@fluentui/react-theme": "^9.1.6", diff --git a/packages/react-components/react-infobutton/src/InfoLabel.ts b/packages/react-components/react-infobutton/src/InfoLabel.ts new file mode 100644 index 00000000000000..32746564357c2d --- /dev/null +++ b/packages/react-components/react-infobutton/src/InfoLabel.ts @@ -0,0 +1 @@ +export * from './components/InfoLabel/index'; diff --git a/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.test.tsx b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.test.tsx new file mode 100644 index 00000000000000..5e8f9ddb9a6a1a --- /dev/null +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.test.tsx @@ -0,0 +1,46 @@ +import * as React from 'react'; + +import { render } from '@testing-library/react'; +import { isConformant } from '../../testing/isConformant'; +import { InfoLabel } from './InfoLabel'; + +describe('InfoLabel', () => { + isConformant({ + Component: InfoLabel, + displayName: 'InfoLabel', + primarySlot: 'label', + testOptions: { + 'has-static-classnames': [ + { + props: { + info: 'Test', + }, + }, + ], + }, + }); + + it('renders an InfoButton when info is set', () => { + const result = render(Test label); + expect(result.getByRole('button')).toBeTruthy(); + }); + + it("renders an InfoButton when the infoButton slot's content is set", () => { + const result = render(Test label); + expect(result.getByRole('button')).toBeTruthy(); + }); + + it('does not render an InfoButton when info is not set', () => { + const result = render(Test label); + expect(result.queryByRole('button')).toBeNull(); + }); + + it('sets the infoButton aria-labelledby to the label and infoButton', () => { + const result = render(Test label); + + const infoButton = result.getByRole('button'); + const label = result.getByText('Test label') as HTMLLabelElement; + + expect(infoButton.getAttribute('aria-labelledby')).toBe(`${label.id} ${infoButton.id}`); + }); +}); diff --git a/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.tsx b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.tsx new file mode 100644 index 00000000000000..ccd509c21f69a9 --- /dev/null +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; + +import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import type { InfoLabelProps } from './InfoLabel.types'; +import { renderInfoLabel_unstable } from './renderInfoLabel'; +import { useInfoLabel_unstable } from './useInfoLabel'; +import { useInfoLabelStyles_unstable } from './useInfoLabelStyles'; + +/** + * InfoLabel component + */ +export const InfoLabel: ForwardRefComponent = React.forwardRef((props, ref) => { + const state = useInfoLabel_unstable(props, ref); + + useInfoLabelStyles_unstable(state); + return renderInfoLabel_unstable(state); +}); + +InfoLabel.displayName = 'InfoLabel'; diff --git a/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.types.ts b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.types.ts new file mode 100644 index 00000000000000..bc268c3c5d0ad7 --- /dev/null +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.types.ts @@ -0,0 +1,41 @@ +import { Label } from '@fluentui/react-label'; +import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import { InfoButton } from '../InfoButton'; +import type { InfoButtonProps } from '../InfoButton'; + +export type InfoLabelSlots = { + root: NonNullable>; + + /** + * The Label component. + * + * It is not typically necessary to use this prop. The label text is the child of the ``, and other props + * such as `size` and `required` should be set directly on the `InfoLabel`. + * + * This is the PRIMARY slot: all native properties specified directly on `` will be applied to this slot, + * except `className` and `style`, which remain on the root slot. + */ + label: NonNullable>; + + /** + * The InfoButton component. + * + * It is not typically necessary to use this prop. The content can be set using the `info` prop of the InfoLabel. + */ + infoButton: Slot; +}; + +/** + * InfoLabel Props + */ +export type InfoLabelProps = ComponentProps, 'label'> & { + /** + * The content of the InfoButton's popover. + */ + info?: InfoButtonProps['content']; +}; + +/** + * State used in rendering InfoLabel + */ +export type InfoLabelState = ComponentState & Pick; diff --git a/packages/react-components/react-infobutton/src/components/InfoLabel/index.ts b/packages/react-components/react-infobutton/src/components/InfoLabel/index.ts new file mode 100644 index 00000000000000..4511700f741cdf --- /dev/null +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/index.ts @@ -0,0 +1,5 @@ +export * from './InfoLabel'; +export * from './InfoLabel.types'; +export * from './renderInfoLabel'; +export * from './useInfoLabel'; +export * from './useInfoLabelStyles'; diff --git a/packages/react-components/react-infobutton/src/components/InfoLabel/renderInfoLabel.tsx b/packages/react-components/react-infobutton/src/components/InfoLabel/renderInfoLabel.tsx new file mode 100644 index 00000000000000..bc2374ed78f990 --- /dev/null +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/renderInfoLabel.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; + +import { getSlots } from '@fluentui/react-utilities'; +import type { InfoLabelSlots, InfoLabelState } from './InfoLabel.types'; + +/** + * Render the final JSX of InfoLabel + */ +export const renderInfoLabel_unstable = (state: InfoLabelState) => { + const { slots, slotProps } = getSlots(state); + + return ( + + + {slots.infoButton && } + + ); +}; diff --git a/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts new file mode 100644 index 00000000000000..02a30345d84605 --- /dev/null +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts @@ -0,0 +1,71 @@ +import * as React from 'react'; + +import { Label } from '@fluentui/react-label'; +import { resolveShorthand, useId } from '@fluentui/react-utilities'; +import { InfoButton } from '../InfoButton/InfoButton'; +import type { InfoLabelProps, InfoLabelState } from './InfoLabel.types'; + +/** + * Create the state required to render InfoLabel. + * + * The returned state can be modified with hooks such as useInfoLabelStyles_unstable, + * before being passed to renderInfoLabel_unstable. + * + * @param props - props from this instance of InfoLabel + * @param ref - reference to label element of InfoLabel + */ +export const useInfoLabel_unstable = (props: InfoLabelProps, ref: React.Ref): InfoLabelState => { + const { + root: rootShorthand, + label: labelShorthand, + infoButton: infoButtonShorthand, + size, + info, + className, + style, + ...labelProps + } = props; + + const root = resolveShorthand(rootShorthand, { + required: true, + defaultProps: { + className, + style, + }, + }); + + const label = resolveShorthand(labelShorthand, { + required: true, + defaultProps: { + id: useId('infolabel-'), + ref, + size, + ...labelProps, + }, + }); + + const infoButton = resolveShorthand(infoButtonShorthand, { + required: !!info, + defaultProps: { + id: useId('infobutton-'), + content: info, + size, + }, + }); + + if (infoButton) { + infoButton['aria-labelledby'] ??= `${label.id} ${infoButton.id}`; + } + + return { + size, + components: { + root: 'span', + label: Label, + infoButton: InfoButton, + }, + root, + label, + infoButton, + }; +}; diff --git a/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabelStyles.ts b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabelStyles.ts new file mode 100644 index 00000000000000..100f087f9fdec2 --- /dev/null +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabelStyles.ts @@ -0,0 +1,56 @@ +import { tokens } from '@fluentui/react-theme'; +import type { SlotClassNames } from '@fluentui/react-utilities'; +import { makeStyles, mergeClasses } from '@griffel/react'; +import type { InfoLabelSlots, InfoLabelState } from './InfoLabel.types'; + +export const infoLabelClassNames: SlotClassNames = { + root: 'fui-InfoLabel', + label: 'fui-InfoLabel__label', + infoButton: 'fui-InfoLabel__infoButton', +}; + +const useLabelStyles = makeStyles({ + base: { + verticalAlign: 'top', + cursor: 'inherit', + color: 'inherit', + }, +}); + +const useInfoButtonStyles = makeStyles({ + base: { + verticalAlign: 'top', + + // Negative margin to align with the text + marginTop: `calc(0px - ${tokens.spacingVerticalXXS})`, + marginBottom: `calc(0px - ${tokens.spacingVerticalXXS})`, + }, + + large: { + // Negative margin to align with the text + marginTop: '-1px', + marginBottom: '-1px', + }, +}); + +/** + * Apply styling to the InfoLabel slots based on the state + */ +export const useInfoLabelStyles_unstable = (state: InfoLabelState): InfoLabelState => { + state.root.className = mergeClasses(infoLabelClassNames.root, state.root.className); + + const labelStyles = useLabelStyles(); + state.label.className = mergeClasses(infoLabelClassNames.label, labelStyles.base, state.label.className); + + const infoButtonStyles = useInfoButtonStyles(); + if (state.infoButton) { + state.infoButton.className = mergeClasses( + infoLabelClassNames.infoButton, + infoButtonStyles.base, + state.size === 'large' && infoButtonStyles.large, + state.infoButton.className, + ); + } + + return state; +}; diff --git a/packages/react-components/react-infobutton/src/index.ts b/packages/react-components/react-infobutton/src/index.ts index 94f120cfd96a84..2f13cdd25b29d4 100644 --- a/packages/react-components/react-infobutton/src/index.ts +++ b/packages/react-components/react-infobutton/src/index.ts @@ -6,3 +6,11 @@ export { useInfoButton_unstable, } from './InfoButton'; export type { InfoButtonProps, InfoButtonSlots, InfoButtonState } from './InfoButton'; +export { + InfoLabel, + infoLabelClassNames, + renderInfoLabel_unstable, + useInfoLabelStyles_unstable, + useInfoLabel_unstable, +} from './InfoLabel'; +export type { InfoLabelProps, InfoLabelSlots, InfoLabelState } from './InfoLabel'; diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelBestPractices.md b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelBestPractices.md new file mode 100644 index 00000000000000..a46580ec87b042 --- /dev/null +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelBestPractices.md @@ -0,0 +1,10 @@ +
+ +Best Practices + + +### Don't + +- Because the Popover isn't always visible, don't include information in the InfoButton's content that people must know in order to complete the field. + +
diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDefault.stories.tsx b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDefault.stories.tsx new file mode 100644 index 00000000000000..6d0892297b69dc --- /dev/null +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDefault.stories.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; + +import { Link } from '@fluentui/react-components'; +import { InfoLabel, InfoLabelProps } from '@fluentui/react-components/unstable'; + +export const Default = (props: Partial) => ( + + This is example content for an InfoButton. Learn more + + } + {...props} + > + Example label + +); diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDescription.md b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDescription.md new file mode 100644 index 00000000000000..f57b24810daa51 --- /dev/null +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDescription.md @@ -0,0 +1,15 @@ +An InfoLabel is a Label with an InfoButton at the end, properly handling layout and accessibility properties. +It can be used as a drop-in replacement for Label when an InfoButton is also needed. + + + +> **⚠️ Preview components are considered unstable:** +> +> ```jsx +> +> import { InfoLabel } from '@fluentui/react-components/unstable'; +> +> ``` +> +> - Features and APIs may change before final release +> - Please contact us if you intend to use this in your product diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelInField.stories.tsx b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelInField.stories.tsx new file mode 100644 index 00000000000000..35a22ccd83c155 --- /dev/null +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelInField.stories.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; + +import { Input, LabelProps } from '@fluentui/react-components'; +import { Field, InfoLabel } from '@fluentui/react-components/unstable'; + +export const InField = () => ( + ( + + Field with info label + + ), + }} + > + + +); + +InField.storyName = 'In a Field'; +InField.parameters = { + docs: { + description: { + story: + 'An `InfoLabel` can be used in a `Field` by rendering the label prop as an InfoLabel. This uses the slot ' + + '[render function]' + + '(./?path=/docs/concepts-developer-customizing-components-with-slots--page#replacing-the-entire-slot) ' + + 'support. See the code from this story for an example.', + }, + }, +}; diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelRequired.stories.tsx b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelRequired.stories.tsx new file mode 100644 index 00000000000000..2b12ba0cdc92b9 --- /dev/null +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelRequired.stories.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; + +import { InfoLabel } from '@fluentui/react-components/unstable'; + +export const Required = () => ( + + Required label + +); + +Required.parameters = { + docs: { + description: { + story: 'When marked `required`, the indicator asterisk is placed before the InfoButton.', + }, + }, +}; diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelSize.stories.tsx b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelSize.stories.tsx new file mode 100644 index 00000000000000..fd5b49b9600db5 --- /dev/null +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelSize.stories.tsx @@ -0,0 +1,39 @@ +import * as React from 'react'; + +import { makeStyles, tokens } from '@fluentui/react-components'; +import { InfoLabel } from '@fluentui/react-components/unstable'; + +const useStyles = makeStyles({ + container: { + alignItems: 'start', + display: 'flex', + flexDirection: 'column', + rowGap: tokens.spacingVerticalL, + }, +}); + +export const Size = () => { + const styles = useStyles(); + + return ( +
+ + Small label + + + Medium label + + + Large label + +
+ ); +}; + +Size.parameters = { + docs: { + description: { + story: "InfoLabel's `size` prop affects the size of the Label and InfoButton. The default size is medium.", + }, + }, +}; diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/index.stories.tsx b/packages/react-components/react-infobutton/stories/InfoLabel/index.stories.tsx new file mode 100644 index 00000000000000..a34cbf0052276d --- /dev/null +++ b/packages/react-components/react-infobutton/stories/InfoLabel/index.stories.tsx @@ -0,0 +1,21 @@ +import { InfoLabel } from '@fluentui/react-infobutton'; + +import descriptionMd from './InfoLabelDescription.md'; +import bestPracticesMd from './InfoLabelBestPractices.md'; + +export { Default } from './InfoLabelDefault.stories'; +export { Required } from './InfoLabelRequired.stories'; +export { Size } from './InfoLabelSize.stories'; +export { InField } from './InfoLabelInField.stories'; + +export default { + title: 'Preview Components/InfoLabel', + component: InfoLabel, + parameters: { + docs: { + description: { + component: [descriptionMd, bestPracticesMd].join('\n'), + }, + }, + }, +}; diff --git a/packages/react-components/react-infobutton/stories/Infobutton/InfoButtonBestPractices.md b/packages/react-components/react-infobutton/stories/Infobutton/InfoButtonBestPractices.md index 1fca418c2bf226..e2b76af455e195 100644 --- a/packages/react-components/react-infobutton/stories/Infobutton/InfoButtonBestPractices.md +++ b/packages/react-components/react-infobutton/stories/Infobutton/InfoButtonBestPractices.md @@ -3,9 +3,10 @@ Best Practices -### Do's +### Do -- Set `aria-labelledby` to the id of the label that the InfoButton provides more information about and the button's id. See the `InfoButton with Label` example below for more information. +- Prefer using an `InfoLabel` if the `InfoButton` is intended to be associated with a label. +- Set `aria-label` to an appropriate value if the `InfoButton` is not associated with a label. ### Don't diff --git a/packages/react-components/react-infobutton/stories/Infobutton/InfoButtonWithLabel.stories.tsx b/packages/react-components/react-infobutton/stories/Infobutton/InfoButtonWithLabel.stories.tsx deleted file mode 100644 index 1b1dfeb863db81..00000000000000 --- a/packages/react-components/react-infobutton/stories/Infobutton/InfoButtonWithLabel.stories.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import * as React from 'react'; -import { InfoButton } from '@fluentui/react-infobutton'; -import { Label, Input, makeStyles, shorthands, useId } from '@fluentui/react-components'; - -const useStyles = makeStyles({ - base: { - alignItems: 'start', - display: 'flex', - flexDirection: 'column', - ...shorthands.padding('20px'), - ...shorthands.gap('10px'), - }, - labelSpacing: { - display: 'flex', - ...shorthands.gap('4px'), - }, -}); - -export const InfoButtonWithLabel = () => { - const styles = useStyles(); - const infoButtonId = useId(); - const inputId = useId(); - const labelId = useId(); - - return ( -
-
- - -
- -
- ); -}; - -InfoButtonWithLabel.parameters = { - docs: { - description: { - story: - "An InfoButton's `aria-labelledby` should include the label's id to correctly" + - ' associate the label with the InfoButton.', - }, - }, -}; diff --git a/packages/react-components/react-infobutton/stories/Infobutton/index.stories.tsx b/packages/react-components/react-infobutton/stories/Infobutton/index.stories.tsx index 34444d5fe6b3e1..65c5f3afaae1d5 100644 --- a/packages/react-components/react-infobutton/stories/Infobutton/index.stories.tsx +++ b/packages/react-components/react-infobutton/stories/Infobutton/index.stories.tsx @@ -5,7 +5,6 @@ import bestPracticesMd from './InfoButtonBestPractices.md'; export { Default } from './InfoButtonDefault.stories'; export { Size } from './InfoButtonSize.stories'; -export { InfoButtonWithLabel } from './InfoButtonWithLabel.stories'; export default { title: 'Preview Components/InfoButton',