From 35b482b814e67ebb5fdc043be24cb36a9ebdac24 Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Tue, 7 Mar 2023 12:57:29 -0800 Subject: [PATCH 01/20] InfoLabel component boilerplate --- .../etc/react-infobutton.api.md | 26 +++++++++++++++ .../react-infobutton/src/InfoLabel.ts | 1 + .../components/InfoLabel/InfoLabel.test.tsx | 18 ++++++++++ .../src/components/InfoLabel/InfoLabel.tsx | 18 ++++++++++ .../components/InfoLabel/InfoLabel.types.ts | 17 ++++++++++ .../__snapshots__/InfoLabel.test.tsx.snap | 11 +++++++ .../src/components/InfoLabel/index.ts | 5 +++ .../components/InfoLabel/renderInfoLabel.tsx | 13 ++++++++ .../src/components/InfoLabel/useInfoLabel.ts | 28 ++++++++++++++++ .../InfoLabel/useInfoLabelStyles.ts | 33 +++++++++++++++++++ .../react-infobutton/src/index.ts | 8 +++++ .../InfoLabel/InfoLabelBestPractices.md | 5 +++ .../InfoLabel/InfoLabelDefault.stories.tsx | 4 +++ .../stories/InfoLabel/InfoLabelDescription.md | 0 .../stories/InfoLabel/index.stories.tsx | 18 ++++++++++ 15 files changed, 205 insertions(+) create mode 100644 packages/react-components/react-infobutton/src/InfoLabel.ts create mode 100644 packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.test.tsx create mode 100644 packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.tsx create mode 100644 packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.types.ts create mode 100644 packages/react-components/react-infobutton/src/components/InfoLabel/__snapshots__/InfoLabel.test.tsx.snap create mode 100644 packages/react-components/react-infobutton/src/components/InfoLabel/index.ts create mode 100644 packages/react-components/react-infobutton/src/components/InfoLabel/renderInfoLabel.tsx create mode 100644 packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts create mode 100644 packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabelStyles.ts create mode 100644 packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelBestPractices.md create mode 100644 packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDefault.stories.tsx create mode 100644 packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDescription.md create mode 100644 packages/react-components/react-infobutton/stories/InfoLabel/index.stories.tsx 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 5a0ebabff8ef9..e4937e041e870 100644 --- a/packages/react-components/react-infobutton/etc/react-infobutton.api.md +++ b/packages/react-components/react-infobutton/etc/react-infobutton.api.md @@ -36,15 +36,41 @@ 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 & {}; + +// @public (undocumented) +export type InfoLabelSlots = { + root: Slot<'div'>; +}; + +// @public +export type InfoLabelState = ComponentState; + // @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/src/InfoLabel.ts b/packages/react-components/react-infobutton/src/InfoLabel.ts new file mode 100644 index 0000000000000..32746564357c2 --- /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 0000000000000..8e880f6a8062a --- /dev/null +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.test.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { render } from '@testing-library/react'; +import { InfoLabel } from './InfoLabel'; +import { isConformant } from '../../testing/isConformant'; + +describe('InfoLabel', () => { + isConformant({ + Component: InfoLabel, + displayName: 'InfoLabel', + }); + + // TODO add more tests here, and create visual regression tests in /apps/vr-tests + + it('renders a default state', () => { + const result = render(Default InfoLabel); + expect(result.container).toMatchSnapshot(); + }); +}); 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 0000000000000..1cdb4c16375d0 --- /dev/null +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { useInfoLabel_unstable } from './useInfoLabel'; +import { renderInfoLabel_unstable } from './renderInfoLabel'; +import { useInfoLabelStyles_unstable } from './useInfoLabelStyles'; +import type { InfoLabelProps } from './InfoLabel.types'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; + +/** + * InfoLabel component - TODO: add more docs + */ +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 0000000000000..c27f5e1163ada --- /dev/null +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.types.ts @@ -0,0 +1,17 @@ +import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; + +export type InfoLabelSlots = { + root: Slot<'div'>; +}; + +/** + * InfoLabel Props + */ +export type InfoLabelProps = ComponentProps & {}; + +/** + * State used in rendering InfoLabel + */ +export type InfoLabelState = ComponentState; +// TODO: Remove semicolon from previous line, uncomment next line, and provide union of props to pick from InfoLabelProps. +// & Required> diff --git a/packages/react-components/react-infobutton/src/components/InfoLabel/__snapshots__/InfoLabel.test.tsx.snap b/packages/react-components/react-infobutton/src/components/InfoLabel/__snapshots__/InfoLabel.test.tsx.snap new file mode 100644 index 0000000000000..f5e93a0966f3e --- /dev/null +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/__snapshots__/InfoLabel.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`InfoLabel renders a default state 1`] = ` +
+
+ Default InfoLabel +
+
+`; 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 0000000000000..4511700f741cd --- /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 0000000000000..8c416dabc7e56 --- /dev/null +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/renderInfoLabel.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import { getSlots } from '@fluentui/react-utilities'; +import type { InfoLabelState, InfoLabelSlots } from './InfoLabel.types'; + +/** + * Render the final JSX of InfoLabel + */ +export const renderInfoLabel_unstable = (state: InfoLabelState) => { + const { slots, slotProps } = getSlots(state); + + // TODO Add additional slots in the appropriate place + return ; +}; 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 0000000000000..03cd53e409e54 --- /dev/null +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts @@ -0,0 +1,28 @@ +import * as React from 'react'; +import { getNativeElementProps } from '@fluentui/react-utilities'; +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 root HTMLElement of InfoLabel + */ +export const useInfoLabel_unstable = (props: InfoLabelProps, ref: React.Ref): InfoLabelState => { + return { + // TODO add appropriate props/defaults + components: { + // TODO add each slot's element type or component + root: 'div', + }, + // TODO add appropriate slots, for example: + // mySlot: resolveShorthand(props.mySlot), + root: getNativeElementProps('div', { + ref, + ...props, + }), + }; +}; 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 0000000000000..ad66a42936608 --- /dev/null +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabelStyles.ts @@ -0,0 +1,33 @@ +import { makeStyles, mergeClasses } from '@griffel/react'; +import type { InfoLabelSlots, InfoLabelState } from './InfoLabel.types'; +import type { SlotClassNames } from '@fluentui/react-utilities'; + +export const infoLabelClassNames: SlotClassNames = { + root: 'fui-InfoLabel', + // TODO: add class names for all slots on InfoLabelSlots. + // Should be of the form `: 'fui-InfoLabel__` +}; + +/** + * Styles for the root slot + */ +const useStyles = makeStyles({ + root: { + // TODO Add default styles for the root element + }, + + // TODO add additional classes for different states and/or slots +}); + +/** + * Apply styling to the InfoLabel slots based on the state + */ +export const useInfoLabelStyles_unstable = (state: InfoLabelState): InfoLabelState => { + const styles = useStyles(); + state.root.className = mergeClasses(infoLabelClassNames.root, styles.root, state.root.className); + + // TODO Add class names to slots, for example: + // state.mySlot.className = mergeClasses(styles.mySlot, state.mySlot.className); + + return state; +}; diff --git a/packages/react-components/react-infobutton/src/index.ts b/packages/react-components/react-infobutton/src/index.ts index 94f120cfd96a8..2f13cdd25b29d 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 0000000000000..08ff8ddeeb5f8 --- /dev/null +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelBestPractices.md @@ -0,0 +1,5 @@ +## Best practices + +### Do + +### Don't 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 0000000000000..8c54288c58e5e --- /dev/null +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDefault.stories.tsx @@ -0,0 +1,4 @@ +import * as React from 'react'; +import { InfoLabel, InfoLabelProps } from '@fluentui/react-infobutton'; + +export const Default = (props: Partial) => ; 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 0000000000000..e69de29bb2d1d 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 0000000000000..2db4c14f2c118 --- /dev/null +++ b/packages/react-components/react-infobutton/stories/InfoLabel/index.stories.tsx @@ -0,0 +1,18 @@ +import { InfoLabel } from '@fluentui/react-infobutton'; + +import descriptionMd from './InfoLabelDescription.md'; +import bestPracticesMd from './InfoLabelBestPractices.md'; + +export { Default } from './InfoLabelDefault.stories'; + +export default { + title: 'Preview Components/InfoLabel', + component: InfoLabel, + parameters: { + docs: { + description: { + component: [descriptionMd, bestPracticesMd].join('\n'), + }, + }, + }, +}; From 1ac74f231e66b2fd77b1878c608f998cc929b021 Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Tue, 7 Mar 2023 13:54:10 -0800 Subject: [PATCH 02/20] Initial implementation of InfoLabel --- .../etc/react-infobutton.api.md | 11 +-- .../react-infobutton/package.json | 1 + .../components/InfoLabel/InfoLabel.test.tsx | 23 +++++-- .../src/components/InfoLabel/InfoLabel.tsx | 9 +-- .../components/InfoLabel/InfoLabel.types.ts | 14 ++-- .../__snapshots__/InfoLabel.test.tsx.snap | 11 --- .../components/InfoLabel/renderInfoLabel.tsx | 11 ++- .../src/components/InfoLabel/useInfoLabel.ts | 54 +++++++++++---- .../InfoLabel/useInfoLabelStyles.ts | 69 +++++++++++++++---- 9 files changed, 145 insertions(+), 58 deletions(-) delete mode 100644 packages/react-components/react-infobutton/src/components/InfoLabel/__snapshots__/InfoLabel.test.tsx.snap 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 e4937e041e870..382b0f350dbba 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'; @@ -43,15 +44,17 @@ export const InfoLabel: ForwardRefComponent; export const infoLabelClassNames: SlotClassNames; // @public -export type InfoLabelProps = ComponentProps & {}; +export type InfoLabelProps = ComponentProps, 'label'>; // @public (undocumented) export type InfoLabelSlots = { - root: Slot<'div'>; + root: NonNullable>; + label: NonNullable>; + infoButton: Slot; }; // @public -export type InfoLabelState = ComponentState; +export type InfoLabelState = ComponentState & Pick; // @public export const renderInfoButton_unstable: (state: InfoButtonState) => JSX.Element; @@ -66,7 +69,7 @@ export const useInfoButton_unstable: (props: InfoButtonProps, ref: React_2.Ref InfoButtonState; // @public -export const useInfoLabel_unstable: (props: InfoLabelProps, ref: React_2.Ref) => InfoLabelState; +export const useInfoLabel_unstable: (props: InfoLabelProps, ref: React_2.Ref) => InfoLabelState; // @public export const useInfoLabelStyles_unstable: (state: InfoLabelState) => InfoLabelState; diff --git a/packages/react-components/react-infobutton/package.json b/packages/react-components/react-infobutton/package.json index 2701ce0e6288a..093a24b44e00d 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.0.22", "@fluentui/react-popover": "^9.4.11", "@fluentui/react-tabster": "^9.5.3", "@fluentui/react-theme": "^9.1.5", 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 index 8e880f6a8062a..a3b4ae503f8db 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.test.tsx +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.test.tsx @@ -1,18 +1,31 @@ import * as React from 'react'; + import { render } from '@testing-library/react'; -import { InfoLabel } from './InfoLabel'; import { isConformant } from '../../testing/isConformant'; +import { InfoLabel } from './InfoLabel'; describe('InfoLabel', () => { isConformant({ Component: InfoLabel, displayName: 'InfoLabel', + primarySlot: 'label', + testOptions: { + 'has-static-classnames': [ + { + props: { + infoButton: { content: 'Test' }, + }, + }, + ], + }, }); - // TODO add more tests here, and create visual regression tests in /apps/vr-tests + 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; - it('renders a default state', () => { - const result = render(Default InfoLabel); - expect(result.container).toMatchSnapshot(); + 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 index 1cdb4c16375d0..ccd509c21f69a 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.tsx +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.tsx @@ -1,12 +1,13 @@ import * as React from 'react'; -import { useInfoLabel_unstable } from './useInfoLabel'; + +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'; -import type { InfoLabelProps } from './InfoLabel.types'; -import type { ForwardRefComponent } from '@fluentui/react-utilities'; /** - * InfoLabel component - TODO: add more docs + * InfoLabel component */ export const InfoLabel: ForwardRefComponent = React.forwardRef((props, ref) => { const state = useInfoLabel_unstable(props, ref); 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 index c27f5e1163ada..070dd6334768d 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.types.ts +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.types.ts @@ -1,17 +1,21 @@ +import { Label } from '@fluentui/react-label'; import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import { InfoButton } from '../InfoButton/InfoButton'; export type InfoLabelSlots = { - root: Slot<'div'>; + root: NonNullable>; + + label: NonNullable>; + + infoButton: Slot; }; /** * InfoLabel Props */ -export type InfoLabelProps = ComponentProps & {}; +export type InfoLabelProps = ComponentProps, 'label'>; /** * State used in rendering InfoLabel */ -export type InfoLabelState = ComponentState; -// TODO: Remove semicolon from previous line, uncomment next line, and provide union of props to pick from InfoLabelProps. -// & Required> +export type InfoLabelState = ComponentState & Pick; diff --git a/packages/react-components/react-infobutton/src/components/InfoLabel/__snapshots__/InfoLabel.test.tsx.snap b/packages/react-components/react-infobutton/src/components/InfoLabel/__snapshots__/InfoLabel.test.tsx.snap deleted file mode 100644 index f5e93a0966f3e..0000000000000 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/__snapshots__/InfoLabel.test.tsx.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`InfoLabel renders a default state 1`] = ` -
-
- Default InfoLabel -
-
-`; diff --git a/packages/react-components/react-infobutton/src/components/InfoLabel/renderInfoLabel.tsx b/packages/react-components/react-infobutton/src/components/InfoLabel/renderInfoLabel.tsx index 8c416dabc7e56..bc2374ed78f99 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/renderInfoLabel.tsx +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/renderInfoLabel.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; + import { getSlots } from '@fluentui/react-utilities'; -import type { InfoLabelState, InfoLabelSlots } from './InfoLabel.types'; +import type { InfoLabelSlots, InfoLabelState } from './InfoLabel.types'; /** * Render the final JSX of InfoLabel @@ -8,6 +9,10 @@ import type { InfoLabelState, InfoLabelSlots } from './InfoLabel.types'; export const renderInfoLabel_unstable = (state: InfoLabelState) => { const { slots, slotProps } = getSlots(state); - // TODO Add additional slots in the appropriate place - return ; + 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 index 03cd53e409e54..64b227dc5aa92 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts @@ -1,6 +1,9 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; + +import { resolveShorthand, useId } from '@fluentui/react-utilities'; import type { InfoLabelProps, InfoLabelState } from './InfoLabel.types'; +import { Label } from '@fluentui/react-label'; +import { InfoButton } from '../InfoButton/InfoButton'; /** * Create the state required to render InfoLabel. @@ -9,20 +12,47 @@ import type { InfoLabelProps, InfoLabelState } from './InfoLabel.types'; * before being passed to renderInfoLabel_unstable. * * @param props - props from this instance of InfoLabel - * @param ref - reference to root HTMLElement of InfoLabel + * @param ref - reference to label element of InfoLabel */ -export const useInfoLabel_unstable = (props: InfoLabelProps, ref: React.Ref): InfoLabelState => { +export const useInfoLabel_unstable = (props: InfoLabelProps, ref: React.Ref): InfoLabelState => { + const { root: rootSlot, label: labelSlot, infoButton: infoButtonSlot, className, style, ...labelProps } = props; + + const root = resolveShorthand(rootSlot, { + required: true, + defaultProps: { + className, + style, + }, + }); + + const label = resolveShorthand(labelSlot, { + required: true, + defaultProps: { + id: useId('infolabel-'), + ref, + ...labelProps, + }, + }); + + const infoButton = resolveShorthand(infoButtonSlot, { + defaultProps: { + id: useId('infobutton-'), + }, + }); + + if (infoButton && !infoButton['aria-labelledby']) { + infoButton['aria-labelledby'] = `${label.id} ${infoButton.id}`; + } + return { - // TODO add appropriate props/defaults + size: props.size, components: { - // TODO add each slot's element type or component - root: 'div', + root: 'span', + label: Label, + infoButton: InfoButton, }, - // TODO add appropriate slots, for example: - // mySlot: resolveShorthand(props.mySlot), - root: getNativeElementProps('div', { - ref, - ...props, - }), + 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 index ad66a42936608..ea59f3096281d 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabelStyles.ts +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabelStyles.ts @@ -1,33 +1,74 @@ +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'; -import type { SlotClassNames } from '@fluentui/react-utilities'; export const infoLabelClassNames: SlotClassNames = { root: 'fui-InfoLabel', - // TODO: add class names for all slots on InfoLabelSlots. - // Should be of the form `: 'fui-InfoLabel__` + label: 'fui-InfoLabel__label', + infoButton: 'fui-InfoLabel__infoButton', }; -/** - * Styles for the root slot - */ -const useStyles = makeStyles({ - root: { - // TODO Add default styles for the root element +const useRootStyles = makeStyles({ + base: { + // This padding must match the negative margin on infoButton + paddingTop: tokens.spacingVerticalXXS, + paddingBottom: tokens.spacingVerticalXXS, }, - // TODO add additional classes for different states and/or slots + large: { + // This padding must match the negative margin on infoButton + paddingTop: '1px', + paddingBottom: '1px', + }, +}); + +const useLabelStyles = makeStyles({ + base: { + verticalAlign: 'top', + }, +}); + +const useInfoButtonStyles = makeStyles({ + base: { + 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', + }, }); /** * Apply styling to the InfoLabel slots based on the state */ export const useInfoLabelStyles_unstable = (state: InfoLabelState): InfoLabelState => { - const styles = useStyles(); - state.root.className = mergeClasses(infoLabelClassNames.root, styles.root, state.root.className); + const rootStyles = useRootStyles(); + state.root.className = mergeClasses( + infoLabelClassNames.root, + rootStyles.base, + state.size === 'large' && rootStyles.large, + state.root.className, + ); + + const labelStyles = useLabelStyles(); + state.label.className = mergeClasses(infoLabelClassNames.label, labelStyles.base, state.label.className); - // TODO Add class names to slots, for example: - // state.mySlot.className = mergeClasses(styles.mySlot, state.mySlot.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; }; From 30b500b41b279bea4f9437618fed6a9ce6cf4c9c Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Tue, 7 Mar 2023 16:19:54 -0800 Subject: [PATCH 03/20] Initial implementation of InfoLabel --- .../components/InfoLabel/InfoLabel.test.tsx | 2 +- .../components/InfoLabel/InfoLabel.types.ts | 19 +++++++++-- .../src/components/InfoLabel/useInfoLabel.ts | 16 ++++++++-- .../InfoLabel/useInfoLabelStyles.ts | 28 +++------------- .../InfoLabel/InfoLabelBestPractices.md | 5 --- .../InfoLabel/InfoLabelDefault.stories.tsx | 7 +++- .../stories/InfoLabel/InfoLabelDescription.md | 1 + .../InfoLabel/InfoLabelInField.stories.tsx | 32 +++++++++++++++++++ .../InfoLabel/InfoLabelRequired.stories.tsx | 17 ++++++++++ .../stories/InfoLabel/index.stories.tsx | 5 +-- 10 files changed, 95 insertions(+), 37 deletions(-) delete mode 100644 packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelBestPractices.md create mode 100644 packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelInField.stories.tsx create mode 100644 packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelRequired.stories.tsx 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 index a3b4ae503f8db..5c9d7e4dfcf34 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.test.tsx +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.test.tsx @@ -13,7 +13,7 @@ describe('InfoLabel', () => { 'has-static-classnames': [ { props: { - infoButton: { content: 'Test' }, + content: 'Test', }, }, ], 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 index 070dd6334768d..375985d166ed4 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.types.ts +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.types.ts @@ -1,19 +1,34 @@ import { Label } from '@fluentui/react-label'; import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; -import { InfoButton } from '../InfoButton/InfoButton'; +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 `content` prop of the InfoLabel. + */ infoButton: Slot; }; /** * InfoLabel Props */ -export type InfoLabelProps = ComponentProps, 'label'>; +export type InfoLabelProps = ComponentProps, 'label'> & Pick; /** * State used in rendering InfoLabel diff --git a/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts index 64b227dc5aa92..4d0c45efc9797 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts @@ -15,7 +15,15 @@ import { InfoButton } from '../InfoButton/InfoButton'; * @param ref - reference to label element of InfoLabel */ export const useInfoLabel_unstable = (props: InfoLabelProps, ref: React.Ref): InfoLabelState => { - const { root: rootSlot, label: labelSlot, infoButton: infoButtonSlot, className, style, ...labelProps } = props; + const { + root: rootSlot, + label: labelSlot, + infoButton: infoButtonSlot, + content, + className, + style, + ...labelProps + } = props; const root = resolveShorthand(rootSlot, { required: true, @@ -35,13 +43,15 @@ export const useInfoLabel_unstable = (props: InfoLabelProps, ref: React.Ref = { infoButton: 'fui-InfoLabel__infoButton', }; -const useRootStyles = 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', - }, -}); - const useLabelStyles = makeStyles({ base: { verticalAlign: 'top', + cursor: 'inherit', + color: 'inherit', }, }); @@ -33,13 +21,13 @@ const useInfoButtonStyles = makeStyles({ base: { verticalAlign: 'top', - // Negative margin to offset the labelWrapper's padding + // Negative margin to align with the text marginTop: `calc(0px - ${tokens.spacingVerticalXXS})`, marginBottom: `calc(0px - ${tokens.spacingVerticalXXS})`, }, large: { - // Negative margin to offset the labelWrapper's padding + // Negative margin to align with the text marginTop: '-1px', marginBottom: '-1px', }, @@ -49,13 +37,7 @@ const useInfoButtonStyles = makeStyles({ * Apply styling to the InfoLabel slots based on the state */ export const useInfoLabelStyles_unstable = (state: InfoLabelState): InfoLabelState => { - const rootStyles = useRootStyles(); - state.root.className = mergeClasses( - infoLabelClassNames.root, - rootStyles.base, - state.size === 'large' && rootStyles.large, - state.root.className, - ); + state.root.className = mergeClasses(infoLabelClassNames.root, state.root.className); const labelStyles = useLabelStyles(); state.label.className = mergeClasses(infoLabelClassNames.label, labelStyles.base, state.label.className); diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelBestPractices.md b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelBestPractices.md deleted file mode 100644 index 08ff8ddeeb5f8..0000000000000 --- a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelBestPractices.md +++ /dev/null @@ -1,5 +0,0 @@ -## Best practices - -### Do - -### Don't diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDefault.stories.tsx b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDefault.stories.tsx index 8c54288c58e5e..4a18336f18472 100644 --- a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDefault.stories.tsx +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDefault.stories.tsx @@ -1,4 +1,9 @@ import * as React from 'react'; + import { InfoLabel, InfoLabelProps } from '@fluentui/react-infobutton'; -export const Default = (props: Partial) => ; +export const Default = (props: Partial) => ( + + Example info label + +); diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDescription.md b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDescription.md index e69de29bb2d1d..05ce3e90af9b9 100644 --- a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDescription.md +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDescription.md @@ -0,0 +1 @@ +An InfoLabel is a Label with an InfoButton at the end. 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 0000000000000..2f666c18c4237 --- /dev/null +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelInField.stories.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; + +import { Input, LabelProps } from '@fluentui/react-components'; +import { Field } from '@fluentui/react-components/unstable'; +import { InfoLabel } from '@fluentui/react-infobutton'; + +export const InField = () => ( + , props: LabelProps) => ( + + 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]' + + '(/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 0000000000000..8ba3f46c838ab --- /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-infobutton'; + +export const Required = () => ( + + Required label + +); + +Required.parameters = { + docs: { + description: { + story: 'When marked `required`, the indicator asterisk is placed before the info button.', + }, + }, +}; diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/index.stories.tsx b/packages/react-components/react-infobutton/stories/InfoLabel/index.stories.tsx index 2db4c14f2c118..8eba2c92d6fc2 100644 --- a/packages/react-components/react-infobutton/stories/InfoLabel/index.stories.tsx +++ b/packages/react-components/react-infobutton/stories/InfoLabel/index.stories.tsx @@ -1,9 +1,10 @@ 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 { InField } from './InfoLabelInField.stories'; export default { title: 'Preview Components/InfoLabel', @@ -11,7 +12,7 @@ export default { parameters: { docs: { description: { - component: [descriptionMd, bestPracticesMd].join('\n'), + component: descriptionMd, }, }, }, From bcbbcde2526bd10e37f851243ac79d7ba029e5e1 Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Tue, 7 Mar 2023 16:27:19 -0800 Subject: [PATCH 04/20] VR tests --- .../src/stories/InfoLabel.stories.tsx | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 apps/vr-tests-react-components/src/stories/InfoLabel.stories.tsx 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 0000000000000..d2bb380f9387a --- /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, + }, + ); From b559925c03ed09410ac6a60955b32eddfebc73a7 Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Tue, 7 Mar 2023 16:27:58 -0800 Subject: [PATCH 05/20] change file --- ...ct-infobutton-31dcdbe1-fdb8-4865-b821-6717fd7a1db1.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-react-infobutton-31dcdbe1-fdb8-4865-b821-6717fd7a1db1.json 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 0000000000000..0a672bb9ada1d --- /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" +} From eebf516724c6f4590c5ba19a0dc660b3d4214caf Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Wed, 8 Mar 2023 11:01:04 -0800 Subject: [PATCH 06/20] Update api.md --- .../react-infobutton/etc/react-infobutton.api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 382b0f350dbba..fdff30387da3f 100644 --- a/packages/react-components/react-infobutton/etc/react-infobutton.api.md +++ b/packages/react-components/react-infobutton/etc/react-infobutton.api.md @@ -44,7 +44,7 @@ export const InfoLabel: ForwardRefComponent; export const infoLabelClassNames: SlotClassNames; // @public -export type InfoLabelProps = ComponentProps, 'label'>; +export type InfoLabelProps = ComponentProps, 'label'> & Pick; // @public (undocumented) export type InfoLabelSlots = { From 64afdbbe5c729df144a9aeb00c300c6b2d40ac7d Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Wed, 8 Mar 2023 16:22:39 -0800 Subject: [PATCH 07/20] Propagate size to InfoButton --- .../src/components/InfoLabel/useInfoLabel.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts index 4d0c45efc9797..3122f152389f8 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts @@ -19,6 +19,7 @@ export const useInfoLabel_unstable = (props: InfoLabelProps, ref: React.Ref Date: Wed, 8 Mar 2023 16:25:38 -0800 Subject: [PATCH 08/20] Add InfoLabel bundle size test --- .../react-infobutton/bundle-size/InfoLabel.fixture.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/react-components/react-infobutton/bundle-size/InfoLabel.fixture.js 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 0000000000000..db396f5802c38 --- /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', +}; From 024e6c3ed9825e10db72fc897b17f6f6cb2fed78 Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Wed, 8 Mar 2023 16:41:53 -0800 Subject: [PATCH 09/20] Add size story --- .../InfoLabel/InfoLabelRequired.stories.tsx | 2 +- .../InfoLabel/InfoLabelSize.stories.tsx | 38 +++++++++++++++++++ .../stories/InfoLabel/index.stories.tsx | 1 + 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelSize.stories.tsx diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelRequired.stories.tsx b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelRequired.stories.tsx index 8ba3f46c838ab..99066b6f930fa 100644 --- a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelRequired.stories.tsx +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelRequired.stories.tsx @@ -11,7 +11,7 @@ export const Required = () => ( Required.parameters = { docs: { description: { - story: 'When marked `required`, the indicator asterisk is placed before the info button.', + 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 0000000000000..7da42d2f7c15d --- /dev/null +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelSize.stories.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import { InfoLabel } from '@fluentui/react-infobutton'; +import { makeStyles, tokens } from '@fluentui/react-components'; + +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 index 8eba2c92d6fc2..cce54d59dc8d5 100644 --- a/packages/react-components/react-infobutton/stories/InfoLabel/index.stories.tsx +++ b/packages/react-components/react-infobutton/stories/InfoLabel/index.stories.tsx @@ -4,6 +4,7 @@ import descriptionMd from './InfoLabelDescription.md'; export { Default } from './InfoLabelDefault.stories'; export { Required } from './InfoLabelRequired.stories'; +export { Size } from './InfoLabelSize.stories'; export { InField } from './InfoLabelInField.stories'; export default { From e5f80ba564473ef4bc00d4eb32299b8a44381bae Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Tue, 14 Mar 2023 11:44:04 -0700 Subject: [PATCH 10/20] Fix doc link in story --- .../stories/InfoLabel/InfoLabelInField.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelInField.stories.tsx b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelInField.stories.tsx index 2f666c18c4237..97926b76fdfa1 100644 --- a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelInField.stories.tsx +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelInField.stories.tsx @@ -25,7 +25,7 @@ InField.parameters = { story: 'An `InfoLabel` can be used in a `Field` by rendering the label prop as an InfoLabel. This uses the slot ' + '[render function]' + - '(/docs/concepts-developer-customizing-components-with-slots--page#replacing-the-entire-slot) ' + + '(./?path=/docs/concepts-developer-customizing-components-with-slots--page#replacing-the-entire-slot) ' + 'support. See the code from this story for an example.', }, }, From c51ed89adfe541436d243023e345e483377f6df1 Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Tue, 14 Mar 2023 11:46:20 -0700 Subject: [PATCH 11/20] Export InfoLabel from react-components/unstable --- .../react-components/src/unstable/index.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/react-components/react-components/src/unstable/index.ts b/packages/react-components/react-components/src/unstable/index.ts index d79893da6a36f..60eb1ef64ef74 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'; From 31a0200c79767ea1810d6d884980ef3cb12b7f40 Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Tue, 14 Mar 2023 12:44:34 -0700 Subject: [PATCH 12/20] api.md --- .../etc/react-components.unstable.api.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) 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 69e11834e36fa..536698590ea8a 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 } From 31878693f368a1c003763509efb1ac39026b755a Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Tue, 14 Mar 2023 13:46:11 -0700 Subject: [PATCH 13/20] Update documentation and stories --- .../InfoLabel/InfoLabelBestPractices.md | 10 ++++ .../InfoLabel/InfoLabelDefault.stories.tsx | 14 ++++-- .../stories/InfoLabel/InfoLabelDescription.md | 16 +++++- .../InfoLabel/InfoLabelInField.stories.tsx | 5 +- .../InfoLabel/InfoLabelRequired.stories.tsx | 2 +- .../InfoLabel/InfoLabelSize.stories.tsx | 9 ++-- .../stories/InfoLabel/index.stories.tsx | 3 +- .../Infobutton/InfoButtonBestPractices.md | 5 +- .../InfoButtonWithLabel.stories.tsx | 50 ------------------- .../stories/Infobutton/index.stories.tsx | 1 - 10 files changed, 49 insertions(+), 66 deletions(-) create mode 100644 packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelBestPractices.md delete mode 100644 packages/react-components/react-infobutton/stories/Infobutton/InfoButtonWithLabel.stories.tsx 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 0000000000000..a46580ec87b04 --- /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 index 4a18336f18472..4396c24e9092a 100644 --- a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDefault.stories.tsx +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDefault.stories.tsx @@ -1,9 +1,17 @@ import * as React from 'react'; -import { InfoLabel, InfoLabelProps } from '@fluentui/react-infobutton'; +import { Link } from '@fluentui/react-components'; +import { InfoLabel, InfoLabelProps } from '@fluentui/react-components/unstable'; export const Default = (props: Partial) => ( - - Example info label + + 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 index 05ce3e90af9b9..f57b24810daa5 100644 --- a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDescription.md +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelDescription.md @@ -1 +1,15 @@ -An InfoLabel is a Label with an InfoButton at the end. +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 index 97926b76fdfa1..3d3f998943e7d 100644 --- a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelInField.stories.tsx +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelInField.stories.tsx @@ -1,13 +1,12 @@ import * as React from 'react'; import { Input, LabelProps } from '@fluentui/react-components'; -import { Field } from '@fluentui/react-components/unstable'; -import { InfoLabel } from '@fluentui/react-infobutton'; +import { Field, InfoLabel } from '@fluentui/react-components/unstable'; export const InField = () => ( , props: LabelProps) => ( + children: (_: unknown, props: LabelProps) => ( Field with info label diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelRequired.stories.tsx b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelRequired.stories.tsx index 99066b6f930fa..0924eeebd8eb3 100644 --- a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelRequired.stories.tsx +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelRequired.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { InfoLabel } from '@fluentui/react-infobutton'; +import { InfoLabel } from '@fluentui/react-components/unstable'; export const Required = () => ( diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelSize.stories.tsx b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelSize.stories.tsx index 7da42d2f7c15d..01c36a2b2c56f 100644 --- a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelSize.stories.tsx +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelSize.stories.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; -import { InfoLabel } from '@fluentui/react-infobutton'; + import { makeStyles, tokens } from '@fluentui/react-components'; +import { InfoLabel } from '@fluentui/react-components/unstable'; const useStyles = makeStyles({ container: { @@ -16,13 +17,13 @@ export const Size = () => { return (
- + Small label - + Medium label - + Large label
diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/index.stories.tsx b/packages/react-components/react-infobutton/stories/InfoLabel/index.stories.tsx index cce54d59dc8d5..a34cbf0052276 100644 --- a/packages/react-components/react-infobutton/stories/InfoLabel/index.stories.tsx +++ b/packages/react-components/react-infobutton/stories/InfoLabel/index.stories.tsx @@ -1,6 +1,7 @@ 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'; @@ -13,7 +14,7 @@ export default { parameters: { docs: { description: { - component: descriptionMd, + 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 1fca418c2bf22..fe816c8346813 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 info button 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 1b1dfeb863db8..0000000000000 --- 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 34444d5fe6b3e..65c5f3afaae1d 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', From 8e0a297470f7e08d101e80c9d4fe73e9f1a10c42 Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Tue, 14 Mar 2023 13:46:39 -0700 Subject: [PATCH 14/20] Add VR test for no InfoButton --- .../src/stories/InfoLabel.stories.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/vr-tests-react-components/src/stories/InfoLabel.stories.tsx b/apps/vr-tests-react-components/src/stories/InfoLabel.stories.tsx index d2bb380f9387a..c57713f9c9cea 100644 --- a/apps/vr-tests-react-components/src/stories/InfoLabel.stories.tsx +++ b/apps/vr-tests-react-components/src/stories/InfoLabel.stories.tsx @@ -40,4 +40,5 @@ storiesOf('InfoLabel', module) { includeRtl: true, }, - ); + ) + .addStory('no-content', () => With no info button, this is just a label); From 00c509a1992b83a28eaef80a5c2eb19cf19a4c9d Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Tue, 14 Mar 2023 13:46:46 -0700 Subject: [PATCH 15/20] Add tests --- .../src/components/InfoLabel/InfoLabel.test.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) 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 index 5c9d7e4dfcf34..473910c716d18 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.test.tsx +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.test.tsx @@ -20,8 +20,23 @@ describe('InfoLabel', () => { }, }); - it('sets the infoButton aria-labelledby to the label and infoButton', () => { + it('renders an InfoButton when content 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 content 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; From f17b83f1a91381f2cc6ff7d2234489893f0679cf Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Tue, 14 Mar 2023 13:46:56 -0700 Subject: [PATCH 16/20] Clean up useInfoLabel --- .../src/components/InfoLabel/useInfoLabel.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts index 3122f152389f8..e82ce93b36731 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts @@ -1,9 +1,9 @@ import * as React from 'react'; -import { resolveShorthand, useId } from '@fluentui/react-utilities'; -import type { InfoLabelProps, InfoLabelState } from './InfoLabel.types'; 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. @@ -16,9 +16,9 @@ import { InfoButton } from '../InfoButton/InfoButton'; */ export const useInfoLabel_unstable = (props: InfoLabelProps, ref: React.Ref): InfoLabelState => { const { - root: rootSlot, - label: labelSlot, - infoButton: infoButtonSlot, + root: rootShorthand, + label: labelShorthand, + infoButton: infoButtonShorthand, size, content, className, @@ -26,7 +26,7 @@ export const useInfoLabel_unstable = (props: InfoLabelProps, ref: React.Ref Date: Tue, 14 Mar 2023 13:51:33 -0700 Subject: [PATCH 17/20] change file --- ...ct-components-fff93a07-f531-47a4-ac89-03ebd885c5dc.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-react-components-fff93a07-f531-47a4-ac89-03ebd885c5dc.json 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 0000000000000..a0352d769d0e9 --- /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" +} From 54bba7abce2ea0edddb83f6b684c0c82813bf5fb Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Tue, 14 Mar 2023 16:06:04 -0700 Subject: [PATCH 18/20] Rename content prop to info. --- .../src/stories/InfoLabel.stories.tsx | 13 ++++++------- .../react-infobutton/etc/react-infobutton.api.md | 4 +++- .../src/components/InfoLabel/InfoLabel.test.tsx | 10 +++++----- .../src/components/InfoLabel/InfoLabel.types.ts | 9 +++++++-- .../src/components/InfoLabel/useInfoLabel.ts | 6 +++--- .../stories/InfoLabel/InfoLabelDefault.stories.tsx | 2 +- .../stories/InfoLabel/InfoLabelInField.stories.tsx | 2 +- .../stories/InfoLabel/InfoLabelRequired.stories.tsx | 2 +- .../stories/InfoLabel/InfoLabelSize.stories.tsx | 6 +++--- 9 files changed, 30 insertions(+), 24 deletions(-) diff --git a/apps/vr-tests-react-components/src/stories/InfoLabel.stories.tsx b/apps/vr-tests-react-components/src/stories/InfoLabel.stories.tsx index c57713f9c9cea..5d8a4b85c2971 100644 --- a/apps/vr-tests-react-components/src/stories/InfoLabel.stories.tsx +++ b/apps/vr-tests-react-components/src/stories/InfoLabel.stories.tsx @@ -10,35 +10,34 @@ storiesOf('InfoLabel', module) .addDecorator(story => ( {story()} )) - .addStory('default', () => This is an info label, { + .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, }, - ) - .addStory('no-content', () => With no info button, this is just a label); + ); 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 fdff30387da3f..1d20b44a13245 100644 --- a/packages/react-components/react-infobutton/etc/react-infobutton.api.md +++ b/packages/react-components/react-infobutton/etc/react-infobutton.api.md @@ -44,7 +44,9 @@ export const InfoLabel: ForwardRefComponent; export const infoLabelClassNames: SlotClassNames; // @public -export type InfoLabelProps = ComponentProps, 'label'> & Pick; +export type InfoLabelProps = ComponentProps, 'label'> & { + info?: InfoButtonProps['content']; +}; // @public (undocumented) export type InfoLabelSlots = { 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 index 473910c716d18..5e8f9ddb9a6a1 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.test.tsx +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.test.tsx @@ -13,15 +13,15 @@ describe('InfoLabel', () => { 'has-static-classnames': [ { props: { - content: 'Test', + info: 'Test', }, }, ], }, }); - it('renders an InfoButton when content is set', () => { - const result = render(Test label); + it('renders an InfoButton when info is set', () => { + const result = render(Test label); expect(result.getByRole('button')).toBeTruthy(); }); @@ -30,13 +30,13 @@ describe('InfoLabel', () => { expect(result.getByRole('button')).toBeTruthy(); }); - it('does not render an InfoButton when content is not set', () => { + 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 result = render(Test label); const infoButton = result.getByRole('button'); const label = result.getByText('Test label') as HTMLLabelElement; 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 index 375985d166ed4..bc268c3c5d0ad 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.types.ts +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/InfoLabel.types.ts @@ -20,7 +20,7 @@ export type InfoLabelSlots = { /** * The InfoButton component. * - * It is not typically necessary to use this prop. The content can be set using the `content` prop of the InfoLabel. + * It is not typically necessary to use this prop. The content can be set using the `info` prop of the InfoLabel. */ infoButton: Slot; }; @@ -28,7 +28,12 @@ export type InfoLabelSlots = { /** * InfoLabel Props */ -export type InfoLabelProps = ComponentProps, 'label'> & Pick; +export type InfoLabelProps = ComponentProps, 'label'> & { + /** + * The content of the InfoButton's popover. + */ + info?: InfoButtonProps['content']; +}; /** * State used in rendering InfoLabel diff --git a/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts index e82ce93b36731..02a30345d8460 100644 --- a/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts +++ b/packages/react-components/react-infobutton/src/components/InfoLabel/useInfoLabel.ts @@ -20,7 +20,7 @@ export const useInfoLabel_unstable = (props: InfoLabelProps, ref: React.Ref) => ( This is example content for an InfoButton. Learn more diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelInField.stories.tsx b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelInField.stories.tsx index 3d3f998943e7d..35a22ccd83c15 100644 --- a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelInField.stories.tsx +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelInField.stories.tsx @@ -7,7 +7,7 @@ export const InField = () => ( ( - + Field with info label ), diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelRequired.stories.tsx b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelRequired.stories.tsx index 0924eeebd8eb3..2b12ba0cdc92b 100644 --- a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelRequired.stories.tsx +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelRequired.stories.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { InfoLabel } from '@fluentui/react-components/unstable'; export const Required = () => ( - + Required label ); diff --git a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelSize.stories.tsx b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelSize.stories.tsx index 01c36a2b2c56f..fd5b49b9600db 100644 --- a/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelSize.stories.tsx +++ b/packages/react-components/react-infobutton/stories/InfoLabel/InfoLabelSize.stories.tsx @@ -17,13 +17,13 @@ export const Size = () => { return (
- + Small label - + Medium label - + Large label
From 35bdcb259d5b538a60c48d56fb5586ac486fe774 Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Wed, 15 Mar 2023 11:57:56 -0700 Subject: [PATCH 19/20] Update dependency version on react-label --- packages/react-components/react-infobutton/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-infobutton/package.json b/packages/react-components/react-infobutton/package.json index 9c28f8c3c8957..2c767b8e7cd7c 100644 --- a/packages/react-components/react-infobutton/package.json +++ b/packages/react-components/react-infobutton/package.json @@ -32,8 +32,8 @@ "@fluentui/scripts-tasks": "*" }, "dependencies": { - "@fluentui/react-label": "^9.1.2", "@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", From dbe07ddbfeb281bfd4df2f7569ef0799ad896c0a Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Wed, 15 Mar 2023 12:00:30 -0700 Subject: [PATCH 20/20] Clean up InfoButtonBestPractices --- .../stories/Infobutton/InfoButtonBestPractices.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-components/react-infobutton/stories/Infobutton/InfoButtonBestPractices.md b/packages/react-components/react-infobutton/stories/Infobutton/InfoButtonBestPractices.md index fe816c8346813..e2b76af455e19 100644 --- a/packages/react-components/react-infobutton/stories/Infobutton/InfoButtonBestPractices.md +++ b/packages/react-components/react-infobutton/stories/Infobutton/InfoButtonBestPractices.md @@ -5,8 +5,8 @@ Best Practices ### Do -- Prefer using an `InfoLabel` if the info button is intended to be associated with a label. -- Set `aria-label` to an appropriate value if the InfoButton is not associated with a label. +- 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