Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
56 changes: 42 additions & 14 deletions packages/react-components/react-field/etc/react-field.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,69 @@

```ts

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

import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import { Input } from '@fluentui/react-input';
import { Label } from '@fluentui/react-label';
import * as React_2 from 'react';
import type { Slot } from '@fluentui/react-utilities';
import type { SlotClassNames } from '@fluentui/react-utilities';
import { SlotClassNames } from '@fluentui/react-utilities';
import type { SlotRenderFunction } from '@fluentui/react-utilities';
import type { SlotShorthandValue } from '@fluentui/react-utilities';

// @public
export type FieldProps<T extends FieldComponent> = ComponentProps<Partial<FieldSlots<T>>, 'field'> & {
orientation?: 'vertical' | 'horizontal';
validationState?: 'error' | 'warning' | 'success';
};

// @public
export type FieldSlots<T extends FieldComponent> = {
root: NonNullable<Slot<'div'>>;
field: SlotComponent<T>;
label?: Slot<typeof Label>;
validationMessage?: Slot<'span'>;
validationMessageIcon?: Slot<'span'>;
hint?: Slot<'span'>;
};

// @public
export const Field: ForwardRefComponent<FieldProps>;
export type FieldState<T extends FieldComponent> = ComponentState<Required<FieldSlots<T>>> & Pick<FieldProps<T>, 'orientation' | 'validationState'> & {
classNames: SlotClassNames<FieldSlots<T>>;
};

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

// @public (undocumented)
export const fieldClassNames: SlotClassNames<FieldSlots>;
export const InputField: ForwardRefComponent<InputFieldProps>;

// @public
export type FieldProps = ComponentProps<FieldSlots> & {};
// @public (undocumented)
export const inputFieldClassNames: SlotClassNames<FieldSlots<FieldComponent>>;

// @public (undocumented)
export type FieldSlots = {
root: Slot<'div'>;
};
export type InputFieldProps = FieldProps<typeof Input>;

// @public
export type FieldState = ComponentState<FieldSlots>;
export const renderField_unstable: <T extends FieldComponent>(state: FieldState<T>) => JSX.Element;

// @public
export const renderField_unstable: (state: FieldState) => JSX.Element;
export const useField_unstable: <T extends FieldComponent>(params: UseFieldParams<T>) => FieldState<T>;

// @public
export const useField_unstable: (props: FieldProps, ref: React_2.Ref<HTMLElement>) => FieldState;
// @public (undocumented)
export type UseFieldParams<T extends FieldComponent> = {
props: FieldProps<T> & OptionalFieldComponentProps;
ref: React_2.Ref<HTMLElement>;
component: T;
classNames: SlotClassNames<FieldSlots<T>>;
labelConnection?: 'htmlFor' | 'aria-labelledby';
};

// @public
export const useFieldStyles_unstable: (state: FieldState) => FieldState;
export const useFieldStyles_unstable: <T extends FieldComponent>(state: FieldState<T>) => void;

// (No @packageDocumentation comment for this package)

Expand Down
4 changes: 4 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,10 @@
"@fluentui/scripts": "^1.0.0"
},
"dependencies": {
"@fluentui/react-context-selector": "^9.0.2",
"@fluentui/react-icons": "^2.0.175",
"@fluentui/react-input": "^9.0.4",
"@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
1 change: 1 addition & 0 deletions packages/react-components/react-field/src/InputField.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components/InputField/index';

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,17 +1,101 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import * as React from 'react';
import { Label } from '@fluentui/react-label';
import type { ComponentProps, ComponentState, Slot, SlotClassNames } from '@fluentui/react-utilities';
import type { SlotComponent } from './SlotComponent.types';

export type FieldSlots = {
root: Slot<'div'>;
/**
* 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.
*/
export type FieldComponent = React.VoidFunctionComponent<
Pick<
React.HTMLAttributes<HTMLElement>,
'id' | 'className' | 'style' | 'aria-labelledby' | 'aria-describedby' | 'aria-invalid' | 'aria-errormessage'
>
>;

/**
* Slots added by Field
*/
export type FieldSlots<T extends FieldComponent> = {
root: NonNullable<Slot<'div'>>;

/**
* 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.
*/
field: SlotComponent<T>;

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

/**
* A message about the validation state. The appearance of the `validationMessage` depends on `validationState`.
*/
validationMessage?: Slot<'span'>;

/**
* The icon associated with the `validationMessage`. If the `validationState` prop is set, this will default to an
* icon corresponding to that state.
*
* This will only be displayed if `validationMessage` is set.
*/
validationMessageIcon?: Slot<'span'>;

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

/**
* Field Props
*/
export type FieldProps = ComponentProps<FieldSlots> & {};
export type FieldProps<T extends FieldComponent> = ComponentProps<Partial<FieldSlots<T>>, 'field'> & {
/**
* The orientation of the label relative to the field component.
* This only affects the label, and not the validationMessage or hint (which always appear below the field component).
*
* @default vertical
*/
orientation?: 'vertical' | 'horizontal';

/**
* The `validationState` affects the color of the `validationMessage`, the `validationMessageIcon`, and for some
* field components, an `validationState="error"` causes the border to become red.
*
* @default undefined
*/
validationState?: 'error' | 'warning' | 'success';
};

/**
* Props that are supported by Field, but not required to be supported by the component that implements field.
*/
export type OptionalFieldComponentProps = {
/**
* Whether the field label should be marked as required.
*/
required?: boolean;

/**
* Size of the field label.
*
* Number sizes will be ignored, but are allowed because the HTML `<input>` element has a prop `size?: number`.
*/
size?: 'small' | 'medium' | 'large' | number;
};

/**
* 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<T extends FieldComponent> = ComponentState<Required<FieldSlots<T>>> &
Pick<FieldProps<T>, 'orientation' | 'validationState'> & {
classNames: SlotClassNames<FieldSlots<T>>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from 'react';
import type { SlotShorthandValue, SlotRenderFunction } from '@fluentui/react-utilities';

//
// TEMPORARY definition of the SlotComponent type, until it is available from react-utilities
//

export type SlotComponent<Type extends React.ComponentType | React.VoidFunctionComponent> = WithSlotShorthandValue<
Type extends React.ComponentType<infer Props>
? // If this is a VoidFunctionComponent that doesn't allow children, add { children?: never }
WithSlotRenderFunction<Props extends { children?: unknown } ? Props : Props & { children?: never }>
: never
>;

//
// TEMPORARY copied versions of the non-exported helper types from react-utilities
//

type WithSlotShorthandValue<Props extends { children?: unknown }> =
| Props
| Extract<SlotShorthandValue, Props['children']>;

type WithSlotRenderFunction<Props extends { children?: unknown }> = Props & {
children?: Props['children'] | SlotRenderFunction<Props>;
};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './Field';
export * from './Field.types';
export * from './renderField';
export * from './useField';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import * as React from 'react';
import { getSlots } from '@fluentui/react-utilities';
import type { FieldState, FieldSlots } from './Field.types';
import type { FieldComponent, FieldSlots, FieldState } from './Field.types';

/**
* Render the final JSX of Field
*/
export const renderField_unstable = (state: FieldState) => {
const { slots, slotProps } = getSlots<FieldSlots>(state);
export const renderField_unstable = <T extends FieldComponent>(state: FieldState<T>) => {
const { slots, slotProps } = getSlots<FieldSlots<FieldComponent>>(state as FieldState<FieldComponent>);

// TODO Add additional slots in the appropriate place
return <slots.root {...slotProps.root} />;
return (
<slots.root {...slotProps.root}>
{slots.label && <slots.label {...slotProps.label} />}
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
{slots.field && <slots.field {...(slotProps.field as any)} />}
{slots.validationMessage && (
<slots.validationMessage {...slotProps.validationMessage}>
{slots.validationMessageIcon && <slots.validationMessageIcon {...slotProps.validationMessageIcon} />}
{slotProps.validationMessage.children}
</slots.validationMessage>
)}
{slots.hint && <slots.hint {...slotProps.hint} />}
</slots.root>
);
};

This file was deleted.

Loading