Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions apps/vr-tests-react-components/src/stories/Field.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Checkbox } from '@fluentui/react-checkbox';
import { Combobox, Dropdown } from '@fluentui/react-combobox';
Copy link
Collaborator

@fabricteam fabricteam Mar 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕵 fluentuiv9 Open the Visual Regressions report to inspect the 7 screenshots

✅ There was 7 screenshots added, 0 screenshots removed, 1868 screenshots unchanged, 0 screenshots with different dimensions and 0 screenshots with visible difference.

unknown 7 screenshots
Image Name Diff(in Pixels) Image Type
Field.infoButton+horizontal.default.chromium.png 0 Added
Field.infoButton+longLabel.default.chromium.png 0 Added
Field.infoButton+noLabel.default.chromium.png 0 Added
Field.infoButton+required.default.chromium.png 0 Added
Field.infoButton+size-large.default.chromium.png 0 Added
Field.infoButton+size-small.default.chromium.png 0 Added
Field.infoButton.default.chromium.png 0 Added

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like size small + infobutton has some alignment issues. The label is position on the top and the infobutton looks to be lower than the label. Is this expected? Same for large, but not as noticeable.

import { Field } from '@fluentui/react-field';
import { Dismiss12Filled } from '@fluentui/react-icons';
import { InfoButton } from '@fluentui/react-infobutton';
import { Input } from '@fluentui/react-input';
import { ProgressBar } from '@fluentui/react-progress';
import { Radio, RadioGroup } from '@fluentui/react-radio';
Expand Down Expand Up @@ -109,6 +110,44 @@ storiesOf('Field', module)
<Checkbox label="Checkbox in a horizontal field" />
</Field>
))
.addStory('infoButton', () => (
<Field label="With info" infoButton={<InfoButton content="Example" />}>
<Input />
</Field>
))
.addStory('infoButton+required', () => (
<Field label="Required with info" required infoButton={<InfoButton content="Example" />}>
<Input />
</Field>
))
.addStory('infoButton+longLabel', () => (
<Field
label="With info button and a very long label that should wrap and the info button to appear on the last line"
infoButton={<InfoButton content="Example" />}
>
<Input />
</Field>
))
.addStory('infoButton+size:small', () => (
<Field label="Small with info" infoButton={<InfoButton content="Example" />} size="small">
<Input size="small" />
</Field>
))
.addStory('infoButton+size:large', () => (
<Field label="Large with info" infoButton={<InfoButton content="Example" />} size="large">
<Input size="large" />
</Field>
))
.addStory('infoButton+noLabel', () => (
<Field infoButton={<InfoButton content="Example" />}>
<Input />
</Field>
))
.addStory('infoButton+horizontal', () => (
<Field orientation="horizontal" label="With info" infoButton={<InfoButton content="Example" />}>
<Input />
</Field>
))
.addStory('Checkbox:error', () => (
<Field validationMessage="Error message">
<Checkbox label="Checkbox in a Field with an error" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "chore: Export new context hooks for InfoButton and Field",
"packageName": "@fluentui/react-components",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "feat: Add infoButton slot to Field",
"packageName": "@fluentui/react-field",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "feat: Add InfoButtonContext to allow other components to set default prop values",
"packageName": "@fluentui/react-infobutton",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import { FieldSlots } from '@fluentui/react-field';
import { FieldState } from '@fluentui/react-field';
import { InfoButton } from '@fluentui/react-infobutton';
import { infoButtonClassNames } from '@fluentui/react-infobutton';
import { InfoButtonContextProvider } from '@fluentui/react-infobutton';
import { InfoButtonContextValue } from '@fluentui/react-infobutton';
import { InfoButtonProps } from '@fluentui/react-infobutton';
import { InfoButtonSlots } from '@fluentui/react-infobutton';
import { InfoButtonState } from '@fluentui/react-infobutton';
Expand Down Expand Up @@ -90,8 +92,10 @@ import { useCardPreview_unstable } from '@fluentui/react-card';
import { useCardPreviewStyles_unstable } from '@fluentui/react-card';
import { useCardStyles_unstable } from '@fluentui/react-card';
import { useField_unstable } from '@fluentui/react-field';
import { useFieldContextValues_unstable } from '@fluentui/react-field';
import { useFieldStyles_unstable } from '@fluentui/react-field';
import { useInfoButton_unstable } from '@fluentui/react-infobutton';
import { useInfoButtonContext } from '@fluentui/react-infobutton';
import { useInfoButtonStyles_unstable } from '@fluentui/react-infobutton';
import { useIntersectionObserver } from '@fluentui/react-virtualizer';
import { useVirtualizer_unstable } from '@fluentui/react-virtualizer';
Expand Down Expand Up @@ -183,6 +187,10 @@ export { InfoButton }

export { infoButtonClassNames }

export { InfoButtonContextProvider }

export { InfoButtonContextValue }

export { InfoButtonProps }

export { InfoButtonSlots }
Expand Down Expand Up @@ -275,10 +283,14 @@ export { useCardStyles_unstable }

export { useField_unstable }

export { useFieldContextValues_unstable }

export { useFieldStyles_unstable }

export { useInfoButton_unstable }

export { useInfoButtonContext }

export { useInfoButtonStyles_unstable }

export { useIntersectionObserver }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,18 @@ export type {
export {
InfoButton,
infoButtonClassNames,
InfoButtonContextProvider,
useInfoButton_unstable,
useInfoButtonContext,
useInfoButtonStyles_unstable,
renderInfoButton_unstable,
} from '@fluentui/react-infobutton';
export type { InfoButtonProps, InfoButtonSlots, InfoButtonState } from '@fluentui/react-infobutton';
export type {
InfoButtonContextValue,
InfoButtonProps,
InfoButtonSlots,
InfoButtonState,
} from '@fluentui/react-infobutton';

// eslint-disable-next-line deprecation/deprecation
export { CheckboxField_unstable as CheckboxField, checkboxFieldClassNames } from '@fluentui/react-checkbox';
Expand Down Expand Up @@ -110,8 +117,9 @@ export {
Field,
fieldClassNames,
renderField_unstable,
useFieldStyles_unstable,
useField_unstable,
useFieldContextValues_unstable,
useFieldStyles_unstable,
} from '@fluentui/react-field';
export type { FieldProps, FieldSlots, FieldState } from '@fluentui/react-field';

Expand Down
10 changes: 8 additions & 2 deletions packages/react-components/react-field/etc/react-field.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import { ForwardRefComponent } from '@fluentui/react-utilities';
import type { InfoButtonContextValue } from '@fluentui/react-infobutton';
import { Label } from '@fluentui/react-label';
import * as React_2 from 'react';
import type { Slot } from '@fluentui/react-utilities';
Expand Down Expand Up @@ -39,13 +40,15 @@ export type FieldProps = Omit<ComponentProps<FieldSlots>, 'children'> & {
export type FieldSlots = {
root: NonNullable<Slot<'div'>>;
label?: Slot<typeof Label>;
infoButton?: Slot<'span'>;
labelWrapper?: Slot<'div'>;
validationMessage?: Slot<'div'>;
validationMessageIcon?: Slot<'span'>;
hint?: Slot<'div'>;
};

// @public
export type FieldState = ComponentState<Required<FieldSlots>> & Required<Pick<FieldProps, 'orientation' | 'validationState'>>;
export type FieldState = ComponentState<Required<FieldSlots>> & Required<Pick<FieldProps, 'orientation' | 'validationState' | 'size'>>;

// @internal @deprecated (undocumented)
export const getDeprecatedFieldClassNames: (controlRootClassName: string) => {
Expand All @@ -64,11 +67,14 @@ export function makeDeprecatedField<ControlProps>(Control: React_2.ComponentType
}): ForwardRefComponent<DeprecatedFieldProps<ControlProps>>;

// @public
export const renderField_unstable: (state: FieldState) => JSX.Element;
export const renderField_unstable: (state: FieldState, contextValues?: FieldContextValues | undefined) => JSX.Element;

// @public
export const useField_unstable: (props: FieldProps, ref: React_2.Ref<HTMLDivElement>) => FieldState;

// @public (undocumented)
export const useFieldContextValues_unstable: (state: FieldState) => FieldContextValues;

// @public
export const useFieldStyles_unstable: (state: FieldState) => void;

Expand Down
1 change: 1 addition & 0 deletions packages/react-components/react-field/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"dependencies": {
"@fluentui/react-context-selector": "^9.1.10",
"@fluentui/react-icons": "^2.0.175",
"@fluentui/react-infobutton": "9.0.0-beta.17",
"@fluentui/react-label": "^9.0.22",
"@fluentui/react-theme": "^9.1.5",
"@fluentui/react-utilities": "^9.6.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import { InfoButton } from '@fluentui/react-infobutton';
import { render } from '@testing-library/react';
import { isConformant } from '../../testing/isConformant';
import { Field } from './index';
Expand All @@ -12,9 +13,9 @@ describe('Field', () => {
{
props: {
label: 'Test label',
infoButton: <InfoButton content="Test info button" />,
hint: 'Test hint',
validationMessage: 'Test validation message',
validationState: 'error',
},
},
],
Expand Down Expand Up @@ -189,4 +190,18 @@ describe('Field', () => {
'aria-required': true,
});
});

it('passes context to InfoButton to allow it to be labelledby the Field label', () => {
const result = render(
<Field label="Test label" infoButton={<InfoButton content="test" />}>
<input />
</Field>,
);

const label = result.getByText('Test label');
const infoButton = result.getByRole('button');

expect(label.id).toBeTruthy();
expect(infoButton.getAttribute('aria-labelledby')).toBe(`${label.id} ${infoButton.id}`);
});
});
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import * as React from 'react';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import { useFieldContextValues_unstable } from '../../contexts/useFieldContextValues';
import type { FieldProps } from './Field.types';
import { renderField_unstable } from './renderField';
import { useField_unstable } from './useField';
import { useFieldStyles_unstable } from './useFieldStyles';

export const Field: ForwardRefComponent<FieldProps> = React.forwardRef((props, ref) => {
const state = useField_unstable(props, ref);
const contextValues = useFieldContextValues_unstable(state);
useFieldStyles_unstable(state);
return renderField_unstable(state);
return renderField_unstable(state, contextValues);
});

Field.displayName = 'Field';
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import type { InfoButtonContextValue } from '@fluentui/react-infobutton';
import { Label } from '@fluentui/react-label';
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';

Expand All @@ -21,6 +22,23 @@ export type FieldSlots = {
*/
label?: Slot<typeof Label>;

/**
* Slot to hold an InfoButton associated with the field's label.
*
* If supplied, it should be a single `<InfoButton />` control.
*
* @example
* ```
* <Field label="..." infoButton={<InfoButton content="..." />} />
* ```
*/
infoButton?: Slot<'span'>;

/**
* Wrapper around the label and infoButton. Only rendered when there is an infoButton.
*/
labelWrapper?: Slot<'div'>;

/**
* A message about the validation state. By default, this is an error message, but it can be a success, warning,
* or custom message by setting `validationState`.
Expand Down Expand Up @@ -100,4 +118,8 @@ export type FieldProps = Omit<ComponentProps<FieldSlots>, 'children'> & {
* State used in rendering Field
*/
export type FieldState = ComponentState<Required<FieldSlots>> &
Required<Pick<FieldProps, 'orientation' | 'validationState'>>;
Required<Pick<FieldProps, 'orientation' | 'validationState' | 'size'>>;

export type FieldContextValues = {
infoButton: InfoButtonContextValue;
};
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import * as React from 'react';
import { InfoButtonContextProvider } from '@fluentui/react-infobutton';
import { getSlots } from '@fluentui/react-utilities';
import type { FieldSlots, FieldState } from './Field.types';
import type { FieldContextValues, FieldSlots, FieldState } from './Field.types';

/**
* Render the final JSX of Field
*/
export const renderField_unstable = (state: FieldState) => {
export const renderField_unstable = (state: FieldState, contextValues?: FieldContextValues) => {
const { slots, slotProps } = getSlots<FieldSlots>(state);

return (
<slots.root {...slotProps.root}>
{slots.label && <slots.label {...slotProps.label} />}
{slots.labelWrapper ? (
<slots.labelWrapper {...slotProps.labelWrapper}>
{slots.label && <slots.label {...slotProps.label} />}
{slots.infoButton && (
<InfoButtonContextProvider value={contextValues?.infoButton}>
<slots.infoButton {...slotProps.infoButton} />
</InfoButtonContextProvider>
)}
</slots.labelWrapper>
) : (
slots.label && <slots.label {...slotProps.label} />
)}
{slotProps.root.children}
{slots.validationMessage && (
<slots.validationMessage {...slotProps.validationMessage}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const useField_unstable = (props: FieldProps, ref: React.Ref<HTMLDivEleme
orientation = 'vertical',
required,
validationState = props.validationMessage ? 'error' : 'none',
size,
size = 'medium',
} = props;

const baseId = useId('field-');
Expand All @@ -43,6 +43,10 @@ export const useField_unstable = (props: FieldProps, ref: React.Ref<HTMLDivEleme
},
});

const infoButton = resolveShorthand(props.infoButton);

const labelWrapper = resolveShorthand(props.labelWrapper, { required: !!infoButton });

const validationMessage = resolveShorthand(props.validationMessage, {
defaultProps: {
id: baseId + '__validationMessage',
Expand Down Expand Up @@ -102,15 +106,20 @@ export const useField_unstable = (props: FieldProps, ref: React.Ref<HTMLDivEleme
return {
orientation,
validationState,
size,
components: {
root: 'div',
labelWrapper: 'div',
label: Label,
infoButton: 'span',
validationMessage: 'div',
validationMessageIcon: 'span',
hint: 'div',
},
root,
labelWrapper,
label,
infoButton,
validationMessageIcon,
validationMessage,
hint,
Expand Down
Loading