From c1172eb5a5671ea413143b7336b5fc97e7e2d79e Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Fri, 27 Jan 2023 16:29:05 -0800 Subject: [PATCH 1/5] Initial updates to spec --- .../react-components/react-field/docs/Spec.md | 65 +++++++++---------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/packages/react-components/react-field/docs/Spec.md b/packages/react-components/react-field/docs/Spec.md index 332d1dc5c9e956..558a8bf334b0b1 100644 --- a/packages/react-components/react-field/docs/Spec.md +++ b/packages/react-components/react-field/docs/Spec.md @@ -2,61 +2,60 @@ ## Background -Field adds a label, validation text, and hint text to form input components. The existing input components (such as `Input` and `Combobox`) are wrapped to create field versions of them (such as `InputField` and `ComboboxField`). +Field adds a label, validation text, and hint text to form input components. It can be added around any input components, such as `Input` or `Combobox` from this library, or intrinsic `` elements, or custom form controls. Epic issue tracking implementation: https://github.com/microsoft/fluentui/issues/19627 ## Prior Art -Existing libraries tend to take one of the following approaches to field. +Existing libraries take one of several approaches to Field. The basic problem that all are trying to solve is to (a) render a label and some descriptive text around a control, and (b) connect that text to the control via `for`/`aria-labelledby`/`aria-describedby`. 1. Include support for label, error text, etc. in the base input component. Libraries using this approach include: - **FluentUI v8** - [`TextField`](https://developer.microsoft.com/en-us/fluentui#/controls/web/textfield), [`Dropdown`](https://developer.microsoft.com/en-us/fluentui#/controls/web/dropdown), [`ChoiceGroup`](https://developer.microsoft.com/en-us/fluentui#/controls/web/choicegroup), etc. - **Spectrum** - [`TextField`](https://react-spectrum.adobe.com/react-spectrum/TextField.html), [`Slider`](https://react-spectrum.adobe.com/react-spectrum/Slider.html), [`RadioGroup`](https://react-spectrum.adobe.com/react-spectrum/RadioGroup.html), etc. 2. Provide a set of components that are manually constructed into a field. This requires manually hooking up the components using props like `htmlFor` and `aria-describedby`. Libraries using this approach include: - **FluentUI v0** - [`FormField`](https://fluentsite.z22.web.core.windows.net/0.64.0/components/form/props#form-field), [`FormLabel`](https://fluentsite.z22.web.core.windows.net/0.64.0/components/form/props#form-label), [`FormMessage`](https://fluentsite.z22.web.core.windows.net/0.64.0/components/form/props#form-message) - - **Ant** - [`Form.Item`](https://ant.design/components/form/#Form.Item) (uses context to do some of the hooking up between the item and the field component). 3. Provide base components without a label or descriptive text, and then Field versions of those controls. Libraries using this approach include: - **FluentUI v0** - [`Input`](https://fluentsite.z22.web.core.windows.net/0.64.0/components/input/props) and [`FormInput`](https://fluentsite.z22.web.core.windows.net/0.64.0/components/form/props#form-input), for example. - **Evergreen UI** - [`TextInput`](https://evergreen.segment.com/components/text-input) and [`TextInputField`](https://evergreen.segment.com/components/text-input#textinputfield), for example. +4. Provide base components without a label or descriptive text, and have a Field (FormItem) component that passes props to its child via context, a render function, or cloneElement. + - **Ant** - [`Form.Item`](https://ant.design/components/form/#Form.Item) (uses context to do some of the hooking up between the control and the Form.Item component). + - **Atlaskit** - [`Field`](https://atlaskit.atlassian.com/packages/design-system/form/docs/fields) (uses a render function as the child of the Field to pass props). -The Field implementation in this spec follows pattern (3). There are Field versions of all components that can be used as form inputs. There are several reasons, including: +The Field implementation in this spec follows pattern (4). Field passes props to its child to connect the field's label and message text. There are several reasons, including: -- **Accessibility**: By combining a base component with the field props into a single component, all of the accessibility props like `htmlFor` and `aria-describedby` are set correctly for "free". -- **Simplicity**: All props related to the component (such as `label`, `id`, `validationState="error"`, etc.) are on the same component, rather than split between multiple components (like separate `Field` and `Input` components). +- **Accessibility**: All of the accessibility props like `htmlFor` and `aria-describedby` are set correctly on the child for "free". +- **Simplicity**: Passing props down to the children allows any form control to be used as the child of Field. - **Consistency**: All of the Field components share a common set of props for the label, validationState, hint, etc. -- **Bundle size**: When the label and other field functionality is not needed, it is still possible to use the base components without pulling in unnecessary dependencies (like `Label` and the field styling). +- **Bundle size**: When the label and other field functionality is not needed, it is still possible to use the core components like `Input` without pulling in unnecessary dependencies (like `Label` and the field styling). ## Sample Code -Each input component has a field version (such as `InputField`, `ComboboxField`, etc.) that includes the features of Field added to that component. - ```jsx <> - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + ``` From bafa2654d3ce7a87fd0929ebe0e4ca07ad4a8f06 Mon Sep 17 00:00:00 2001 From: Ben Howell Date: Mon, 30 Jan 2023 12:32:57 -0800 Subject: [PATCH 2/5] chore: Update Field spec and migration guide --- .../react-field/docs/Migration.md | 87 ++++--- .../react-components/react-field/docs/Spec.md | 217 ++++++++---------- 2 files changed, 160 insertions(+), 144 deletions(-) diff --git a/packages/react-components/react-field/docs/Migration.md b/packages/react-components/react-field/docs/Migration.md index e967cb966c251c..8cde62e974a3b9 100644 --- a/packages/react-components/react-field/docs/Migration.md +++ b/packages/react-components/react-field/docs/Migration.md @@ -2,41 +2,70 @@ ## Migration from v8 -Migration from v8 will require picking between the normal and `Field` version of an input control, depending on whether the field-specific features are required: (`label`, `validationState="error"`, `validationMessage`, `hint`) +Many form controls in v8 have `label` and `errorMessage` props. The v9 form controls generally don't include those props, and instead require wrapping with `` to add a label and error message. -See individual input components for more detailed migration guides. +### Prop mapping -| v8 Control | v9 Base control | v9 Field control | Notes | -| ------------- | --------------------- | ------------------------------- | -------------------------------------------------------------------------------------------- | -| `Checkbox` | `Checkbox` | `CheckboxField` | Only use `CheckboxField` if an error message is needed, or if required for layout in a form. | -| `ChoiceGroup` | `RadioGroup` | `RadioGroupField` | | -| `ComboBox` | `Combobox` | `ComboboxField` | `errorMessage="..."` is replaced by `validationState="error" validationMessage="..."` | -| `Dropdown` | `Dropdown` | `DropdownField` | `errorMessage="..."` is replaced by `validationState="error" validationMessage="..."` | -| `Slider` | `Slider` | `SliderField` | | -| `SpinButton` | `SpinButton` | `SpinButtonField` | | -| `TextField` | `Input` OR `Textarea` | `InputField` OR `TextareaField` | `errorMessage="..."` is replaced by `validationState="error" validationMessage="..."` | -| `Toggle` | `Switch` | `SwitchField` | | +- `label` on the control => Wrap with `` +- `errorMessage` on the control => Wrap with `` -## Migration from v0 +### Example + +#### v8 + +```jsx + +``` + +#### v9 + +```jsx + + + +``` + +### Special case + +For `Checkbox` in v9, it is recommended to continue using ``, and not to use `Field` to label the `Checkbox`. It is still ok to use `Field` if an error message is needed, or if required for layout in a form. E.g.: -Many components in v0 have `Form___` versions (such as `FormInput`). Those are replaced by the `___Field` equivalent. See the underlying component's migration guides for more detailed migration information. +```jsx + + + +``` -Component mapping: +See individual form components for more detailed migration guides. -- `FormButton` => Not supported -- `FormCheckbox` => `CheckboxField` OR `SwitchField` -- `FormDatepicker` => _(Not yet implemented)_ -- `FormDropdown` => `DropdownField` -- `FormField` => Not supported -- `FormFieldCustom` => Not supported -- `FormLabel` => The `label` prop of the field component -- `FormMessage` => Either the `validationMessage` or `hint` prop of the field component -- `FormRadioGroup` => `RadioGroupField` -- `FormSlider` => `SliderField` -- `FormTextArea` => `TextareaField` +## Migration from v0 + +The v0 `FormField` component is the equivalent to the v9 `Field`. Additionally, many components in v0 have `Form___` versions (such as ``). In v9, these are instead standard inputs wrapped with `` (such as ``). -The following props are common to each of the `Form___` components: +### Prop mapping for `FormField` => `Field`: +- `accessibility` => Not supported (Field is acessible as a form field by default) +- `as` => `as` (only `div` is supported) +- `control` slot => The child of the `Field` +- `errorMessage` => `validationMessage` +- `inline` => `orientation="horizontal"` (note: still uses a block layout but puts the label to the left instead of above) - `label` => `label` -- `message` => either `validationMessage` or `hint` -- `errorMessage` => `validationMessage` with `validationState="error"` +- `message` => `hint`, or `validationMessage` with `validationState` set to `"success"` or `"warning"` +- `name` => Not supported (set `name` on the child input element if needed) +- `required` => `required` +- `type` => Not supported (set `type` on the child input element if needed) + +### Example + +#### v0 + +```jsx + +``` + +#### v9 + +```jsx + + + +``` diff --git a/packages/react-components/react-field/docs/Spec.md b/packages/react-components/react-field/docs/Spec.md index 558a8bf334b0b1..2f6488c77be8df 100644 --- a/packages/react-components/react-field/docs/Spec.md +++ b/packages/react-components/react-field/docs/Spec.md @@ -2,7 +2,7 @@ ## Background -Field adds a label, validation text, and hint text to form input components. It can be added around any input components, such as `Input` or `Combobox` from this library, or intrinsic `` elements, or custom form controls. +Field adds a label, validation text, and hint text to form input components. It can be added around any input components, such as `` or `` from this library, or intrinsic `` elements, or custom form controls. Epic issue tracking implementation: https://github.com/microsoft/fluentui/issues/19627 @@ -26,7 +26,7 @@ The Field implementation in this spec follows pattern (4). Field passes props to - **Accessibility**: All of the accessibility props like `htmlFor` and `aria-describedby` are set correctly on the child for "free". - **Simplicity**: Passing props down to the children allows any form control to be used as the child of Field. -- **Consistency**: All of the Field components share a common set of props for the label, validationState, hint, etc. +- **Consistency**: The Field component provides props for the label, validationState, hint, etc. - **Bundle size**: When the label and other field functionality is not needed, it is still possible to use the core components like `Input` without pulling in unnecessary dependencies (like `Label` and the field styling). ## Sample Code @@ -59,118 +59,105 @@ The Field implementation in this spec follows pattern (4). Field passes props to ``` -These field versions of the components use a common set of Field hooks, and can be defined using very little code. - -```ts -export type InputFieldProps = FieldProps; - -export const InputField: ForwardRefComponent = React.forwardRef((props, ref) => { - const state = useField_unstable(props, ref, Input); - useFieldStyles_unstable(state); - return renderField_unstable(state); -}); - -InputField.displayName = 'InputField'; -``` - -## Components - -The following field components will be defined. If more form components are added in the future, they should also include a Field version. - -- `CheckboxField` -- `ComboboxField` -- `DropdownField` -- `InputField` -- `RadioGroupField` -- `SelectField` -- `SliderField` -- `SpinButtonField` -- `SwitchField` -- `TextareaField` - ## Variants - **Orientation**: The `orientation` prop affects the layout of the label and field component: - `'vertical'` (default) - label is above the field component - `'horizontal'` - label is to the left of the field component, and is 33% the width of the field (this allows multiple stacked fields to all align their labels) - **Validation state**: The `validationState` prop affects the icon and color used by the `validationMessage`: - - `'error'` - Red x icon, red text color + - `'error'` - (default when there is a `validationMessage`): Red x icon, red text color - `'warning'` - Yellow exclamation icon, neutral color text - `'success'` - Green check icon, neutral color text - - `undefined` (default): No validation message icon, neutral color text -- **Error**: Some control types (like `Input` and `Combobox`) have a prop that makes the border red. This prop will be set `validationState="error"`. + - `'none'` - No validation message icon, neutral color text +- **Error**: When `validationState` is `'error'`, Field -Field also forwards some props from the wrapped component to the label as well: +Field also forwards some props to its Label: -- **Size**: If the wrapped component supports a `size` prop, it will also be applied to the field's label. +- **Size**: Affects the size of the Label text (but not validationMessage or hint text). - **Required**: If set, the Label will get a required asterisk: `*` ## API -### FieldComponent +`Field` applies props to its child component, to connect the label and message text to the control, and make the component accessible by default. + +The props added are: + +- `id` - An ID is generated if not present on the child. This is used as the label's `htmlFor`. +- `aria-labelledby` - The label's ID. +- `aria-describedby` - The validationMessage and/or hint's ID. +- `aria-invalid` - If validationState is error (which is the default when a validationMessage is set). +- `aria-required` - If the required prop is set. + +This is done one of two ways: -The `FieldComponent` type defines the minimum set of props that the wrapped component must support. This is used for the generic types as the requirement for the type parameter: `FieldProps` +- If the child is a component, uses `cloneElement` to add the props to the child's props. +- If the child is a render function, passes the props to the render function. That function is expected to spread the props in the appropriate place in its render tree. + +### FieldChildProps + +The `FieldChildProps` type defines the props that may be set on the child of Field (or passed to the child render function). ```ts /** - * The minimum requirement for a component used by Field. - * - * Note: the use of VoidFunctionComponent means that component is not *required* to have a children prop, - * but it is still allowed to have a children prop. + * The props added to the Field's child element. Or if the child is a render function, the props passed to the function. */ -export type FieldComponent = React.VoidFunctionComponent< - Pick< - React.HTMLAttributes, - 'id' | 'className' | 'style' | 'aria-labelledby' | 'aria-describedby' | 'aria-invalid' | 'aria-errormessage' - > +export type FieldChildProps = Pick< + React.HTMLAttributes, + 'id' | 'aria-labelledby' | 'aria-describedby' | 'aria-invalid' | 'aria-required' >; ``` ### Slots -_Note: TypeScript crashes if the `Slot` type is used with a template type parameter. The `SlotComponent` type is a simplified version of that type, which only supports `React.ComponentType`/`React.VoidFunctionComponent`._ - ```ts -export type FieldSlots = { +export type FieldSlots = { root: NonNullable>; - /** - * The underlying component wrapped by this field. - * - * This is the PRIMARY slot: all intrinsic HTML properties will be applied to this slot, - * except `className` and `style`, which remain on the root slot. - */ - control: SlotComponent; - /** * The label associated with the field. */ label?: Slot; /** - * A message about the validation state. The appearance of the `validationMessage` depends on `validationState`. + * 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`. */ - validationMessage?: Slot<'span'>; + validationMessage?: Slot<'div'>; /** - * The icon associated with the `validationMessage`. If the `validationState` prop is set, this will default to an - * icon corresponding to that state. + * The icon associated with the `validationMessage`. This will only be displayed if `validationMessage` is set. * - * This will only be displayed if `validationMessage` is set. + * The default depends on `validationState`: + * * `error` - `` + * * `warning` - `` + * * `success` - `` + * * `none` - `null` */ validationMessageIcon?: Slot<'span'>; /** * Additional hint text below the field. */ - hint?: Slot<'span'>; + hint?: Slot<'div'>; }; ``` ### Props ```ts -export type FieldProps = ComponentProps>, 'control'> & { +export type FieldProps = Omit, 'children'> & { + /** + * The Field's child can be a single form control, or a render function that takes the props that should be spread on + * a form control. + * + * All form controls in this library can be used directly as children (such as `` or ``), as well + * as intrinsic form controls like `` or `