Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2861069
refactor(onboarding-ui): remove forwardRef and simplify component str…
tassoevan Jun 30, 2026
8fa3e0e
refactor(fuselage): simplify Box component by removing unnecessary re…
tassoevan Jun 30, 2026
746e285
refactor(fuselage): replace forwardRef with function components for A…
tassoevan Jun 30, 2026
7fbd1ce
refactor(fuselage): replace forwardRef with function component for Au…
tassoevan Jun 30, 2026
c950cb4
refactor(fuselage): remove explicit references to `forwardRef` types
tassoevan Jun 30, 2026
286ccaa
refactor(fuselage): review types of `InputBox` derived components
tassoevan Jun 30, 2026
598cf98
refactor(fuselage): prefer `RefAttributes<T>` type
tassoevan Jun 30, 2026
2fd9e2c
refactor(fuselage): remove some `forwardRef` usage
tassoevan Jun 30, 2026
af0f83f
refactor(fuselage): remove some `forwardRef` usage
tassoevan Jun 30, 2026
8314de9
refactor(fuselage): remove some `forwardRef` usage
tassoevan Jun 30, 2026
a1c294d
refactor(fuselage-forms): remove `forwardRef` from HOCs
tassoevan Jun 30, 2026
30abe0e
refactor(layout): remove `forwardRef` from `Form`
tassoevan Jul 1, 2026
9e48688
refactor(fuselage): remove some `forwardRef` usage
tassoevan Jul 1, 2026
3301a75
refactor(fuselage): remove some `forwardRef` usage
tassoevan Jul 1, 2026
2a9d74a
refactor(fuselage): remove some `forwardRef` usage
tassoevan Jul 1, 2026
3b35114
refactor(layout): remove `forwardRef` and clean up imports
tassoevan Jul 1, 2026
6d237fb
refactor(fuselage): remove `isForwardRefType` helper function
tassoevan Jul 1, 2026
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
222 changes: 127 additions & 95 deletions packages/fuselage-forms/fuselage-forms.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ComponentPropsWithoutRef } from 'react';
import { Context } from 'react';
import { Dispatch } from 'react';
import { ElementType } from 'react';
import { EmailInputProps } from '@rocket.chat/fuselage';
import { Field as Field_2 } from '@rocket.chat/fuselage';
import { FieldDescription as FieldDescription_2 } from '@rocket.chat/fuselage';
import { FieldError as FieldError_2 } from '@rocket.chat/fuselage';
Expand All @@ -22,7 +23,6 @@ import { FieldLabel as FieldLabel_2 } from '@rocket.chat/fuselage';
import { FieldLabelInfo } from '@rocket.chat/fuselage';
import { FieldLink } from '@rocket.chat/fuselage';
import { FieldRow } from '@rocket.chat/fuselage';
import { ForwardRefExoticComponent } from 'react';
import { IconProps } from '@rocket.chat/fuselage';
import { InputBoxProps } from '@rocket.chat/fuselage';
import { JSX } from 'react';
Expand All @@ -34,25 +34,31 @@ import { PasswordInputProps } from '@rocket.chat/fuselage';
import { ReactNode } from 'react';
import { RefAttributes } from 'react';
import type { RefCallback } from 'react';
import { SearchInputProps } from '@rocket.chat/fuselage';
import { SelectOption } from '@rocket.chat/fuselage';
import { SetStateAction } from 'react';
import { Slider as Slider_2 } from '@rocket.chat/fuselage';
import { TelephoneInputProps } from '@rocket.chat/fuselage';
import { TextAreaInputProps } from '@rocket.chat/fuselage';
import { UrlInputProps } from '@rocket.chat/fuselage';

// @public (undocumented)
export const AutoComplete: typeof AutoComplete_2;

// @public (undocumented)
export const CheckBox: ForwardRefExoticComponent<Omit<BoxProps & {
indeterminate?: boolean;
labelChildren?: ReactNode;
} & AllHTMLAttributes<HTMLInputElement> & RefAttributes<HTMLInputElement>, "ref"> & RefAttributes<HTMLInputElement>>;
export const CheckBox: {
(props: Omit<BoxProps, "ref"> & RefAttributes<HTMLInputElement> & {
indeterminate?: boolean;
labelChildren?: ReactNode;
} & AllHTMLAttributes<HTMLInputElement>): JSX.Element;
displayName: string;
};

// @public (undocumented)
export const EmailInput: ForwardRefExoticComponent<Omit<Omit<InputBoxProps, "type"> & {
addon?: ReactNode;
error?: string;
} & RefAttributes<HTMLElement>, "ref"> & RefAttributes<HTMLElement>>;
export const EmailInput: {
(props: EmailInputProps & RefAttributes<HTMLInputElement>): JSX.Element;
displayName: string;
};

// @public (undocumented)
export function Field(input: FieldProps): JSX.Element;
Expand Down Expand Up @@ -143,63 +149,78 @@ export type LabelForProps = ComponentProps<typeof FieldLabel_2>;
export type LabelTypes = 'hint' | 'description' | 'error' | 'placeholder';

// @public (undocumented)
export const MultiSelect: ForwardRefExoticComponent<Omit<Omit<BoxProps, "value" | "onChange"> & {
value?: SelectOption[0][];
error?: string;
options: SelectOption[];
onChange: (params: SelectOption[0][]) => void;
getLabel?: (params: SelectOption) => SelectOption[1];
getValue?: (params: SelectOption) => SelectOption[0];
customEmpty?: string;
anchor?: ElementType<MultiSelectAnchorParams> | ((params: MultiSelectAnchorParams) => ReactNode);
renderOptions?: ElementType;
renderItem?: ElementType;
renderSelected?: ElementType< {
value: SelectOption[0];
label: SelectOption[1];
onMouseDown: MouseEventHandler;
children: ReactNode;
}>;
addonIcon?: IconProps["name"];
setFilter?: (filter: string) => void;
} & RefAttributes<HTMLInputElement>, "ref"> & RefAttributes<HTMLInputElement>>;

// @public (undocumented)
export const MultiSelectFiltered: ForwardRefExoticComponent<Omit<Omit<BoxProps, "value" | "onChange"> & {
value?: SelectOption[0][];
error?: string;
options: SelectOption[];
onChange: (params: SelectOption[0][]) => void;
getLabel?: (params: SelectOption) => SelectOption[1];
getValue?: (params: SelectOption) => SelectOption[0];
customEmpty?: string;
anchor?: ElementType<MultiSelectAnchorParams> | ((params: MultiSelectAnchorParams) => ReactNode);
renderOptions?: ElementType;
renderItem?: ElementType;
renderSelected?: ElementType< {
value: SelectOption[0];
label: SelectOption[1];
onMouseDown: MouseEventHandler;
children: ReactNode;
}>;
addonIcon?: IconProps["name"];
setFilter?: (filter: string) => void;
} & {
filter?: string;
setFilter?: Dispatch<SetStateAction<string>>;
addonIcon?: IconProps["name"];
} & RefAttributes<HTMLInputElement>, "ref"> & RefAttributes<HTMLInputElement>>;

// @public (undocumented)
export const NumberInput: ForwardRefExoticComponent<Omit<NumberInputProps & RefAttributes<HTMLInputElement>, "ref"> & RefAttributes<HTMLInputElement>>;

// @public (undocumented)
export const PasswordInput: ForwardRefExoticComponent<Omit<PasswordInputProps & RefAttributes<HTMLInputElement>, "ref"> & RefAttributes<HTMLInputElement>>;

// @public (undocumented)
export const RadioButton: ForwardRefExoticComponent<Omit<BoxProps & AllHTMLAttributes<HTMLInputElement> & {
labelChildren?: ReactNode;
} & RefAttributes<HTMLInputElement>, "ref"> & RefAttributes<HTMLInputElement>>;
export const MultiSelect: {
(props: Omit<BoxProps, "ref" | "value" | "onChange"> & RefAttributes<HTMLInputElement> & {
value?: SelectOption[0][];
error?: string;
options: SelectOption[];
onChange: (params: SelectOption[0][]) => void;
getLabel?: (params: SelectOption) => SelectOption[1];
getValue?: (params: SelectOption) => SelectOption[0];
customEmpty?: string;
anchor?: ElementType<MultiSelectAnchorParams> | ((params: MultiSelectAnchorParams) => ReactNode);
renderOptions?: ElementType;
renderItem?: ElementType;
renderSelected?: ElementType< {
value: SelectOption[0];
label: SelectOption[1];
onMouseDown: MouseEventHandler;
children: ReactNode;
}>;
addonIcon?: IconProps["name"];
setFilter?: (filter: string) => void;
}): JSX.Element;
displayName: string;
};

// @public (undocumented)
export const MultiSelectFiltered: {
(props: Omit<BoxProps, "ref" | "value" | "onChange"> & RefAttributes<HTMLInputElement> & {
value?: SelectOption[0][];
error?: string;
options: SelectOption[];
onChange: (params: SelectOption[0][]) => void;
getLabel?: (params: SelectOption) => SelectOption[1];
getValue?: (params: SelectOption) => SelectOption[0];
customEmpty?: string;
anchor?: ElementType<MultiSelectAnchorParams> | ((params: MultiSelectAnchorParams) => ReactNode);
renderOptions?: ElementType;
renderItem?: ElementType;
renderSelected?: ElementType< {
value: SelectOption[0];
label: SelectOption[1];
onMouseDown: MouseEventHandler;
children: ReactNode;
}>;
addonIcon?: IconProps["name"];
setFilter?: (filter: string) => void;
} & {
filter?: string;
setFilter?: Dispatch<SetStateAction<string>>;
addonIcon?: IconProps["name"];
}): JSX.Element;
displayName: string;
};

// @public (undocumented)
export const NumberInput: {
(props: NumberInputProps & RefAttributes<HTMLInputElement>): JSX.Element;
displayName: string;
};

// @public (undocumented)
export const PasswordInput: {
(props: PasswordInputProps & RefAttributes<HTMLInputElement>): JSX.Element;
displayName: string;
};

// @public (undocumented)
export const RadioButton: {
(props: Omit<BoxProps, "ref"> & RefAttributes<HTMLInputElement> & AllHTMLAttributes<HTMLInputElement> & {
labelChildren?: ReactNode;
}): JSX.Element;
displayName: string;
};

// @public (undocumented)
export const ReferencedLabel: (input: ReferencedLabelProps) => JSX.Element;
Expand All @@ -208,50 +229,61 @@ export const ReferencedLabel: (input: ReferencedLabelProps) => JSX.Element;
export type ReferencedLabelProps = ComponentProps<typeof FieldLabel_2>;

// @public (undocumented)
export const SearchInput: ForwardRefExoticComponent<Omit<Omit<InputBoxProps, "type"> & {
addon?: ReactNode;
error?: string;
} & RefAttributes<HTMLInputElement>, "ref"> & RefAttributes<HTMLInputElement>>;
export const SearchInput: {
(props: SearchInputProps & RefAttributes<HTMLInputElement>): JSX.Element;
displayName: string;
};

// @public (undocumented)
export const Select: ForwardRefExoticComponent<Omit<Omit<AriaSelectProps<object>, "children" | "value" | "onChange"> & {
error?: string;
placeholder?: string;
value?: Key | null | undefined;
onChange?: ((key: Key) => any) | undefined;
options: SelectOption[];
small?: boolean;
} & Omit<AllHTMLAttributes<HTMLElement>, "value" | "onChange"> & RefAttributes<HTMLElement>, "ref"> & RefAttributes<HTMLElement>>;
export const Select: {
(props: Omit<AriaSelectProps<unknown>, "children" | "value" | "onChange"> & RefAttributes<HTMLElement> & {
error?: string;
placeholder?: string;
value?: Key | null | undefined;
onChange?: ((key: Key) => any) | undefined;
options: SelectOption[];
small?: boolean;
} & Omit<AllHTMLAttributes<HTMLElement>, "value" | "onChange">): JSX.Element;
displayName: string;
};

// @public (undocumented)
export const Slider: typeof Slider_2;

// @public (undocumented)
export const TelephoneInput: ForwardRefExoticComponent<Omit<Omit<InputBoxProps, "type"> & {
addon?: ReactNode;
input?: ReactNode;
error?: string;
} & RefAttributes<HTMLInputElement>, "ref"> & RefAttributes<HTMLInputElement>>;
export const TelephoneInput: {
(props: TelephoneInputProps & RefAttributes<HTMLInputElement>): JSX.Element;
displayName: string;
};

// @public (undocumented)
export const TextAreaInput: ForwardRefExoticComponent<Omit<Omit<InputBoxProps, "type"> & {
addon?: ReactNode;
error?: string;
} & RefAttributes<HTMLTextAreaElement>, "ref"> & RefAttributes<HTMLTextAreaElement>>;
export const TextAreaInput: {
(props: TextAreaInputProps & RefAttributes<HTMLTextAreaElement>): JSX.Element;
displayName: string;
};

// @public (undocumented)
export const TextInput: ForwardRefExoticComponent<Omit<Omit<InputBoxProps, "type"> & {
addon?: ReactNode;
error?: string;
} & RefAttributes<HTMLInputElement>, "ref"> & RefAttributes<HTMLInputElement>>;
export const TextInput: {
(props: Omit<InputBoxProps, "ref" | "type"> & RefAttributes<HTMLInputElement> & {
addon?: ReactNode;
error?: string;
}): JSX.Element;
displayName: string;
};

// @public (undocumented)
export const ToggleSwitch: ForwardRefExoticComponent<Omit<BoxProps & AllHTMLAttributes<HTMLInputElement> & {
labelChildren?: ReactNode;
} & RefAttributes<HTMLInputElement>, "ref"> & RefAttributes<HTMLInputElement>>;
export const ToggleSwitch: {
(props: Omit<BoxProps, "ref"> & RefAttributes<HTMLInputElement> & AllHTMLAttributes<HTMLInputElement> & {
labelChildren?: ReactNode;
}): JSX.Element;
displayName: string;
};

// @public (undocumented)
export const UrlInput: ForwardRefExoticComponent<Omit<UrlInputProps & RefAttributes<HTMLElement>, "ref"> & RefAttributes<HTMLElement>>;
export const UrlInput: {
(props: UrlInputProps & RefAttributes<HTMLInputElement>): JSX.Element;
displayName: string;
};

// @public (undocumented)
export const useFieldDescriptorId: (type: LabelTypes) => string;
Expand Down
45 changes: 13 additions & 32 deletions packages/fuselage-forms/src/Inputs/withLabelHelpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
/* eslint-disable react/no-multi-comp */
import { useMergedRefs } from '@rocket.chat/fuselage-hooks';
import type { ReactNode, ComponentType, Ref, RefAttributes } from 'react';
import { forwardRef } from 'react';
import { VisuallyHidden } from 'react-aria';

import {
Expand All @@ -17,16 +16,10 @@ type WithLabelId = { id?: string };
function withLabelId<TProps, TRef>(
Component: ComponentType<TProps & WithLabelId & RefAttributes<TRef>>,
) {
const WrappedComponent = forwardRef<TRef, TProps>(function (props, ref) {
function WrappedComponent(props: TProps & RefAttributes<TRef>) {
const labelProps = useFieldReferencedByInput();
return (
<Component
{...(props as TProps & WithLabelId)}
{...labelProps}
ref={ref}
/>
);
});
return <Component {...props} {...labelProps} />;
}

WrappedComponent.displayName = `withLabelId(${Component.displayName ?? Component.name ?? 'InputComponent'})`;

Expand All @@ -38,16 +31,10 @@ type WithLabelledBy = { 'aria-labelledby'?: string };
function withAriaLabelledBy<TProps, TRef>(
Component: ComponentType<TProps & WithLabelledBy & RefAttributes<TRef>>,
) {
const WrappedComponent = forwardRef<TRef, TProps>(function (props, ref) {
function WrappedComponent(props: TProps & RefAttributes<TRef>) {
const labelProps = useFieldReferencedByLabel();
return (
<Component
{...(props as TProps & WithLabelledBy)}
{...labelProps}
ref={ref}
/>
);
});
return <Component {...props} {...labelProps} />;
}

WrappedComponent.displayName = `withAriaLabelledBy(${Component.displayName ?? Component.name ?? 'InputComponent'})`;

Expand All @@ -59,16 +46,10 @@ type WithLabelledByAndId = { 'aria-labelledby'?: string; 'id'?: string };
function withAriaLabelledByAndId<TProps, TRef>(
Component: ComponentType<TProps & WithLabelledByAndId & RefAttributes<TRef>>,
) {
const WrappedComponent = forwardRef<TRef, TProps>(function (props, ref) {
function WrappedComponent(props: TProps & RefAttributes<TRef>) {
const labelProps = useFieldReferencedByLabelWithId();
return (
<Component
{...(props as TProps & WithLabelledByAndId)}
{...labelProps}
ref={ref}
/>
);
});
return <Component {...props} {...labelProps} />;
}

WrappedComponent.displayName = `withAriaLabelledByAndId(${Component.displayName ?? Component.name ?? 'InputComponent'})`;

Expand All @@ -80,18 +61,18 @@ type WithChildrenLabel = { labelChildren: ReactNode };
function withVisuallyHiddenLabel<TProps, TRef>(
Component: ComponentType<TProps & WithChildrenLabel & RefAttributes<TRef>>,
) {
const WrappedComponent = forwardRef<TRef, TProps>(function (props, ref) {
function WrappedComponent(props: TProps & RefAttributes<TRef>) {
const [label, labelProps, labelRef] = useFieldWrappedByInputLabel();
const mergedRef = useMergedRefs(ref, labelRef as Ref<TRef>);
const mergedRef = useMergedRefs(props.ref, labelRef as Ref<TRef>);
return (
<Component
{...(props as TProps & WithChildrenLabel)}
{...props}
{...labelProps}
ref={mergedRef}
labelChildren={<VisuallyHidden>{label}</VisuallyHidden>}
/>
);
});
}

WrappedComponent.displayName = `withVisuallyHiddenLabel(${Component.displayName ?? Component.name ?? 'InputComponent'})`;

Expand Down
Loading
Loading