Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5b16e1a
Initial field implementation
behowell Aug 1, 2022
5a28bfa
Add size stories
behowell Aug 1, 2022
d4bbcc3
Updates to field
behowell Aug 2, 2022
0832e33
Remove commented wrapper
behowell Aug 2, 2022
befddb0
Remove gridTemplateAreas from root styles
behowell Aug 2, 2022
784b898
Update field implementation
behowell Aug 3, 2022
e1942ba
Fix styling for labelBefore
behowell Aug 3, 2022
c5febb0
Documentation updates
behowell Aug 3, 2022
c947a91
Fix styles when labelPosition="before" but there is no label
behowell Aug 4, 2022
1a39484
Update field layout styles
behowell Aug 4, 2022
972619a
Add documentation
behowell Aug 4, 2022
0300845
Add field stories
behowell Aug 4, 2022
f2ea3ac
Update label-before styles
behowell Aug 4, 2022
dd626b2
Fix conformance test, update snapshots and api.md
behowell Aug 4, 2022
feeb5f8
Merge branch 'master' of https://github.com/microsoft/fluentui into f…
behowell Aug 4, 2022
7a8e8c6
Remove unused conformance test
behowell Aug 4, 2022
db6f133
Delete unused useFieldChildProps.ts
behowell Aug 4, 2022
90dae41
syncpack
behowell Aug 4, 2022
06d68bd
Fix imports for stories
behowell Aug 4, 2022
d3e8423
Add htmlFor prop to Field
behowell Aug 4, 2022
3b44699
Update stories
behowell Aug 4, 2022
b723ee1
Merge branch 'master' of https://github.com/microsoft/fluentui into f…
behowell Aug 4, 2022
f5eca60
Update api.md
behowell Aug 4, 2022
f155c49
Comment out debug check until #24236 is fixed
behowell Aug 5, 2022
27b6a82
Fix build error
behowell Aug 5, 2022
edb821a
Merge branch 'master' of https://github.com/microsoft/fluentui into f…
behowell Aug 6, 2022
d811770
Merge branch 'master' of https://github.com/microsoft/fluentui into f…
behowell Aug 8, 2022
1daa45c
Merge branch 'master' of https://github.com/microsoft/fluentui into f…
behowell Aug 12, 2022
011d29c
Merge branch 'master' of https://github.com/behowell/fluentui into fi…
behowell Aug 16, 2022
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
56 changes: 48 additions & 8 deletions packages/react-components/react-field/etc/react-field.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@

```ts

/// <reference types="react" />

import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import { ContextSelector } from '@fluentui/react-context-selector';
import { FC } from 'react';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import { Label } from '@fluentui/react-label';
import { Provider } from 'react';
import { ProviderProps } from 'react';
import * as React_2 from 'react';
import type { Slot } from '@fluentui/react-utilities';
import type { SlotClassNames } from '@fluentui/react-utilities';
Expand All @@ -15,30 +22,63 @@ import type { SlotClassNames } from '@fluentui/react-utilities';
export const Field: ForwardRefComponent<FieldProps>;

// @public (undocumented)
export const fieldClassName = "fui-Field";
export const fieldClassNames: SlotClassNames<FieldSlots>;

// @public (undocumented)
export const fieldClassNames: SlotClassNames<FieldSlots>;
export type FieldContextValue = Readonly<Pick<FieldState, 'generatedChildId' | 'labelId' | 'required' | 'size' | 'status'>>;

// @public (undocumented)
export type FieldContextValues = {
field: FieldContextValue;
};

// @public
export type FieldProps = ComponentProps<FieldSlots> & {};
export type FieldProps = Omit<ComponentProps<Partial<FieldSlots>>, 'children'> & {
children: React_2.ReactElement<{
id?: string;
}>;
labelPosition?: 'above' | 'before';
required?: boolean;
size?: 'small' | 'medium' | 'large';
status?: 'error' | 'warning' | 'success';
htmlFor?: string;
};

// @public (undocumented)
export const FieldProvider: Provider<Readonly<Pick<FieldState, "status" | "required" | "size" | "generatedChildId" | "labelId">> | undefined> & FC<ProviderProps<Readonly<Pick<FieldState, "status" | "required" | "size" | "generatedChildId" | "labelId">> | undefined>>;

// @public (undocumented)
export type FieldSlots = {
root: Slot<'div'>;
root: NonNullable<Slot<'div'>>;
label?: Slot<typeof Label>;
statusText?: Slot<'span'>;
statusIcon?: Slot<'span'>;
helperText?: Slot<'span'>;
};

// @public
export type FieldState = ComponentState<FieldSlots>;
export type FieldState = ComponentState<Required<FieldSlots>> & Pick<FieldProps, 'required' | 'status'> & Required<Pick<FieldProps, 'labelPosition' | 'size'>> & {
generatedChildId: string | undefined;
labelId: string | undefined;
};

// @public (undocumented)
export const filterFieldSize: <SupportedSizes extends "small" | "medium" | "large">(size: FieldProps['size'], supportedSizes: SupportedSizes[]) => SupportedSizes | undefined;

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

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

// @public (undocumented)
export const useFieldContext: <T>(selector: ContextSelector<Readonly<Pick<FieldState, "status" | "required" | "size" | "generatedChildId" | "labelId">> | undefined, T>) => T;

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

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

// (No @packageDocumentation comment for this package)

Expand Down
3 changes: 3 additions & 0 deletions packages/react-components/react-field/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
"@fluentui/scripts": "^1.0.0"
},
"dependencies": {
"@fluentui/react-context-selector": "^9.0.2",
"@fluentui/react-icons": "^2.0.175",
"@fluentui/react-label": "^9.0.4",
"@fluentui/react-theme": "^9.0.0",
"@fluentui/react-utilities": "^9.0.2",
"@griffel/react": "^1.3.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,44 @@ import * as React from 'react';
import { render } from '@testing-library/react';
import { Field } from './Field';
import { isConformant } from '../../common/isConformant';
import { fieldClassNames } from './useFieldStyles';

describe('Field', () => {
isConformant({
Component: Field,
displayName: 'Field',
requiredProps: {
children: <input id="test-input" />,
},
testOptions: {
'has-static-classnames': [
{
props: {
label: 'Label',
status: 'error',
statusText: 'Status text',
helperText: 'Helper text',
},
expectedClassNames: {
root: fieldClassNames.root,
label: fieldClassNames.label,
statusText: fieldClassNames.statusText,
statusIcon: fieldClassNames.statusIcon,
helperText: fieldClassNames.helperText,
},
},
],
},
});

// TODO add more tests here, and create visual regression tests in /apps/vr-tests

it('renders a default state', () => {
const result = render(<Field>Default Field</Field>);
const result = render(
<Field>
<input />
</Field>,
);
expect(result.container).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import * as React from 'react';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import type { FieldProps } from './Field.types';
import { useField_unstable } from './useField';
import { renderField_unstable } from './renderField';
import { useFieldStyles_unstable } from './useFieldStyles';
import type { FieldProps } from './Field.types';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import { useFieldContextValues } from '../../contexts/useFieldContextValues';

/**
* Field component - TODO: add more docs
*/
export const Field: ForwardRefComponent<FieldProps> = React.forwardRef((props, ref) => {
const state = useField_unstable(props, ref);
const contextValues = useFieldContextValues(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,17 +1,112 @@
import { Label } from '@fluentui/react-label';
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import * as React from 'react';

export type FieldSlots = {
root: Slot<'div'>;
root: NonNullable<Slot<'div'>>;

/**
* The label associated with the field.
*/
label?: Slot<typeof Label>;

/**
* A status or validation message. The appearance of the statusText depends on the value of the `status` prop.
*/
statusText?: Slot<'span'>;

/**
* The icon associated with the status. If the `status` prop is set, this will default to a corresponding icon.
*
* This will only be displayed if `statusText` is set.
*/
statusIcon?: Slot<'span'>;

/**
* Additional text below the field.
*/
helperText?: Slot<'span'>;
};

/**
* Field Props
*/
export type FieldProps = ComponentProps<FieldSlots> & {};
export type FieldProps = Omit<ComponentProps<Partial<FieldSlots>>, 'children'> & {
/**
* Field must have exactly one child that is a form component.
*/
children: React.ReactElement<{ id?: string }>;

/**
* The position of the label relative to the field. This only affects the label, and not the statusText or helperText
* (which always appear below the field).
*
* @default above
*/
labelPosition?: 'above' | 'before';

/**
* Marks the field as required, and adds required styling to the label (red asterisk).
*
* @default false
*/
required?: boolean;

/**
* Size of the field and label.
*
* NOTE: Not all components support all available sizes. Check the documentation of the component to see what values
* it supports for its `size` prop.
*
* @default medium
*/
size?: 'small' | 'medium' | 'large';

/**
* The status affects the color of the statusText, the statusIcon, and for some field components, an error status
* causes the border to become red.
*
* @default undefined
*/
status?: 'error' | 'warning' | 'success';

/**
* The ID of the form component in this Field (the child of the Field).
*
* `htmlFor` will default to the `id` property of the Field's child, if set.
* Otherwise, it will default to `generatedChildId` on the FieldContext.
*
* In most cases, it isn't necessary to set this property. It only needs to be set if the child component
* doesn't assign its ID via an `id` prop, and doesn't use FieldContext (such as a component from another library).
*/
htmlFor?: string;
};

/**
* State used in rendering Field
*/
export type FieldState = ComponentState<FieldSlots>;
// TODO: Remove semicolon from previous line, uncomment next line, and provide union of props to pick from FieldProps.
// & Required<Pick<FieldProps, 'propName'>>
export type FieldState = ComponentState<Required<FieldSlots>> &
Pick<FieldProps, 'required' | 'status'> &
Required<Pick<FieldProps, 'labelPosition' | 'size'>> & {
/**
* The generated ID used as the label's htmlFor prop.
*
* This will be undefined if either (a) the Field's htmlFor was set, or (b) the child has an id property set.
*/
generatedChildId: string | undefined;

/**
* The (generated) ID of the field's label component.
*
* This can be used as the `aria-labelledby` prop when the label's htmlFor doesn't work (such as RadioGroup).
*/
labelId: string | undefined;
};

export type FieldContextValue = Readonly<
Pick<FieldState, 'generatedChildId' | 'labelId' | 'required' | 'size' | 'status'>
>;

export type FieldContextValues = {
field: FieldContextValue;
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ exports[`Field renders a default state 1`] = `
<div
class="fui-Field"
>
Default Field
<input />
</div>
</div>
`;
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import * as React from 'react';
import { getSlots } from '@fluentui/react-utilities';
import type { FieldState, FieldSlots } from './Field.types';
import type { FieldState, FieldSlots, FieldContextValues } from './Field.types';
import { FieldContext } from '../../contexts/FieldContext';

/**
* 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);

// TODO Add additional slots in the appropriate place
return <slots.root {...slotProps.root} />;
return (
<FieldContext.Provider value={contextValues.field}>
<slots.root {...slotProps.root}>
{slots.label && <slots.label {...slotProps.label} />}
{slotProps.root.children}
{slots.statusText && (
<slots.statusText {...slotProps.statusText}>
{slots.statusIcon && <slots.statusIcon {...slotProps.statusIcon} />}
{slotProps.statusText.children}
</slots.statusText>
)}
{slots.helperText && <slots.helperText {...slotProps.helperText} />}
</slots.root>
</FieldContext.Provider>
);
};

This file was deleted.

Loading