diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fe1314d..c2ec28a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # NHS.UK React components +## Unreleased + +This version provides support for NHS.UK frontend v10.2 and includes: + +- [Password input component](https://service-manual.nhs.uk/design-system/components/password-input) +- [Smaller and inline buttons](https://service-manual.nhs.uk/design-system/components/buttons#smaller-buttons) +- [Text input styles for codes and sequences](https://service-manual.nhs.uk/design-system/components/text-input#codes-and-sequences) +- [Select](https://service-manual.nhs.uk/design-system/components/select) dividers using `` + +For a full list of changes in this release please refer to the [migration doc](https://github.com/NHSDigital/nhsuk-react-components/blob/main/docs/upgrade-to-6.0.md). + ## 6.0.0-beta.4 - 5 November 2025 This version provides support for NHS.UK frontend v10.1 and includes: diff --git a/docs/upgrade-to-6.0.md b/docs/upgrade-to-6.0.md index 9c002616..1c780090 100644 --- a/docs/upgrade-to-6.0.md +++ b/docs/upgrade-to-6.0.md @@ -18,10 +18,70 @@ The updated [header](https://service-manual.nhs.uk/design-system/components/head - update NHS logo in the header to have higher contrast when focused - refactor CSS classes and BEM naming, use hidden attributes instead of modifier classes, use generic search element +#### Use the password input component to help users accessibly enter passwords + +The [password input](https://service-manual.nhs.uk/design-system/components/password-input) component from NHS.UK frontend v10.2 allows users to choose: + +- whether their passwords are visible or not +- to enter their passwords in plain text + +This helps users use longer and more complex passwords without needing to remember what they've already typed. + #### Smaller versions of radio buttons and checkboxes You can now use smaller versions of the [radios](https://service-manual.nhs.uk/design-system/components/radios) and [checkboxes](https://service-manual.nhs.uk/design-system/components/checkboxes) components by adding the `small` prop. +#### Smaller versions of buttons + +You can now use smaller versions of [buttons](https://service-manual.nhs.uk/design-system/components/buttons) by adding the `small` prop. + +#### Add inline buttons to text inputs and select menus + +You can now add inline buttons to text inputs and select menus using the `formGroupProps.afterInput` prop. + +```jsx + ( + + ), + }}, +/> +``` + +#### Add a 'code' prop for text inputs that accept codes and sequences + +We've added a new `code` prop for the [text input](https://service-manual.nhs.uk/design-system/components/text-input) component. This improves readability of text inputs that receive codes and sequences (like NHS numbers, security codes or booking references). + +```patch + +``` + +#### Add a 'divider' between select options + +Newer browsers support [using `
` (horizontal rule) elements inside a ` + First name (A to Z) + First name (Z to A) ++ + Last name (A to Z) + Last name (Z to A) + +``` + ### Numbered pagination component The [pagination](https://service-manual.nhs.uk/design-system/components/pagination) component from NHS.UK frontend v10.1 has been updated to support numbered pagination: diff --git a/package.json b/package.json index a4221b4b..72a1a0f2 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "jest-axe": "^10.0.0", "jest-environment-jsdom": "^30.2.0", "lodash": "^4.17.21", - "nhsuk-frontend": "^10.1.0", + "nhsuk-frontend": "^10.2.2", "outdent": "^0.8.0", "prettier": "^3.7.4", "react": "^19.2.3", @@ -116,7 +116,7 @@ }, "peerDependencies": { "classnames": ">=2.5.0", - "nhsuk-frontend": ">=10.1.0 <11.0.0", + "nhsuk-frontend": ">=10.2.0 <11.0.0", "react": ">=18.2.0", "react-dom": ">=18.2.0", "tslib": ">=2.8.0" diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index c6a86f28..9daf3b7f 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -101,6 +101,7 @@ describe('Index', () => { 'PaginationLinkText', 'Panel', 'PanelTitle', + 'PasswordInput', 'Radios', 'RadiosContext', 'RadiosDivider', @@ -110,6 +111,7 @@ describe('Index', () => { 'Row', 'SearchIcon', 'Select', + 'SelectDivider', 'SelectOption', 'SkipLink', 'SummaryList', diff --git a/src/components/form-elements/button/Button.tsx b/src/components/form-elements/button/Button.tsx index 0d03b9c5..d2fb207e 100644 --- a/src/components/form-elements/button/Button.tsx +++ b/src/components/form-elements/button/Button.tsx @@ -14,25 +14,38 @@ import { import { type AsElementLink } from '#util/types/LinkTypes.js'; -export interface ButtonProps extends AsElementLink { +export interface ButtonProps extends ButtonBaseProps, AsElementLink { href?: never; - secondary?: boolean; - reverse?: boolean; - warning?: boolean; as?: 'button'; - preventDoubleClick?: boolean; } -export interface ButtonLinkProps extends AsElementLink { +export interface ButtonLinkProps extends ButtonBaseProps, AsElementLink { href: string; type?: never; + as?: 'a'; +} + +interface ButtonBaseProps { secondary?: boolean; reverse?: boolean; warning?: boolean; - as?: 'a'; + login?: boolean; + small?: boolean; preventDoubleClick?: boolean; } +function getButtonClassNames(props: ButtonProps | ButtonLinkProps) { + return classNames( + 'nhsuk-button', + { 'nhsuk-button--secondary': props.secondary }, + { 'nhsuk-button--reverse': props.reverse }, + { 'nhsuk-button--warning': props.warning }, + { 'nhsuk-button--login': props.login }, + { 'nhsuk-button--small': props.small }, + props.className, + ); +} + const ButtonComponent = forwardRef((props, forwardedRef) => { const { className, @@ -41,6 +54,8 @@ const ButtonComponent = forwardRef((props, forwa secondary, reverse, warning, + login, + small, type = 'submit', preventDoubleClick, onClick, @@ -70,13 +85,7 @@ const ButtonComponent = forwardRef((props, forwa return ( ( secondary, reverse, warning, + login, + small, preventDoubleClick, onClick, ...rest @@ -132,13 +143,7 @@ const ButtonLinkComponent = forwardRef( return ( , - Omit { +export interface CharacterCountElementProps extends ComponentPropsWithoutRef<'textarea'> { maxLength?: number; maxWords?: number; threshold?: number; } +export type CharacterCountProps = CharacterCountElementProps & + Omit< + FormElementProps, + 'fieldsetProps' | 'legend' | 'legendProps' + >; + export const CharacterCount = forwardRef( ({ maxLength, maxWords, threshold, formGroupProps, ...rest }, forwardedRef) => { const moduleRef = useRef(null); @@ -47,7 +50,7 @@ export const CharacterCount = forwardRef + inputType="textarea" formGroupProps={{ ...formGroupProps, diff --git a/src/components/form-elements/checkboxes/Checkboxes.tsx b/src/components/form-elements/checkboxes/Checkboxes.tsx index 5e490269..28e1ab04 100644 --- a/src/components/form-elements/checkboxes/Checkboxes.tsx +++ b/src/components/form-elements/checkboxes/Checkboxes.tsx @@ -18,12 +18,14 @@ import { FormGroup } from '#components/utils/index.js'; import { generateRandomName } from '#util/tools/index.js'; import { type FormElementProps } from '#util/types/FormTypes.js'; -export interface CheckboxesProps - extends ComponentPropsWithoutRef<'div'>, Omit { +export interface CheckboxesElementProps extends ComponentPropsWithoutRef<'div'> { idPrefix?: string; small?: boolean; } +export type CheckboxesProps = CheckboxesElementProps & + Omit, 'label' | 'labelProps'>; + const CheckboxesComponent = forwardRef((props, forwardedRef) => { const { children, idPrefix, ...rest } = props; @@ -82,7 +84,7 @@ const CheckboxesComponent = forwardRef((props, } return ( - inputType="checkboxes" {...rest}> + inputType="checkboxes" {...rest}> {({ className, small, name, id, idPrefix, error, ...restRenderProps }) => { resetCheckboxIds(); const contextValue: ICheckboxesContext = { diff --git a/src/components/form-elements/checkboxes/components/CheckboxesItem.tsx b/src/components/form-elements/checkboxes/components/CheckboxesItem.tsx index a2cc66a6..84d401ab 100644 --- a/src/components/form-elements/checkboxes/components/CheckboxesItem.tsx +++ b/src/components/form-elements/checkboxes/components/CheckboxesItem.tsx @@ -17,16 +17,19 @@ import { HintText } from '#components/form-elements/hint-text/index.js'; import { Label } from '#components/form-elements/label/index.js'; import { type ComponentPropsWithDataAttributes, type FormElementProps } from '#util/types/index.js'; -export interface CheckboxesItemProps - extends - ComponentPropsWithoutRef<'input'>, - Pick { +export interface CheckboxesItemElementProps extends ComponentPropsWithoutRef<'input'> { conditional?: ReactNode; forceShowConditional?: boolean; conditionalProps?: ComponentPropsWithRef<'div'>; exclusive?: boolean; } +export type CheckboxesItemProps = CheckboxesItemElementProps & + Omit< + FormElementProps, + 'fieldsetProps' | 'label' | 'legend' | 'legendProps' + >; + export const CheckboxesItem = forwardRef( (props, forwardedRef) => { const { diff --git a/src/components/form-elements/date-input/DateInput.tsx b/src/components/form-elements/date-input/DateInput.tsx index 0836e23c..b34726ab 100644 --- a/src/components/form-elements/date-input/DateInput.tsx +++ b/src/components/form-elements/date-input/DateInput.tsx @@ -36,15 +36,21 @@ export interface DateInputElement extends Omit; } -export interface DateInputProps - extends - Omit, 'defaultValue' | 'onChange'>, - Omit { +export interface DateInputElementProps extends Omit< + ComponentPropsWithoutRef<'div'>, + 'defaultValue' | 'onChange' +> { value?: Partial; defaultValue?: Partial; onChange?: EventHandler; } +export type DateInputProps = DateInputElementProps & + Omit< + FormElementProps, 'defaultValue' | 'onChange'>, 'div'>, + 'label' | 'labelProps' + >; + export type DateInputType = 'day' | 'month' | 'year'; const DateInputComponent = forwardRef( @@ -78,7 +84,7 @@ const DateInputComponent = forwardRef( }; return ( - > + , 'div'> formGroupProps={{ ...formGroupProps, ref: moduleRef }} fieldsetProps={{ role: 'group' }} inputType="dateinput" diff --git a/src/components/form-elements/date-input/components/DateInputField.tsx b/src/components/form-elements/date-input/components/DateInputField.tsx index 55e02baf..da36f9ad 100644 --- a/src/components/form-elements/date-input/components/DateInputField.tsx +++ b/src/components/form-elements/date-input/components/DateInputField.tsx @@ -14,12 +14,17 @@ import { DateInputContext, type IDateInputContext } from '../DateInputContext.js import { Label } from '#components/form-elements/label/index.js'; import { type FormElementProps } from '#util/types/FormTypes.js'; -export interface IndividualDateInputProps - extends ComponentPropsWithoutRef<'input'>, Pick { +export interface IndividualDateInputElementProps extends ComponentPropsWithoutRef<'input'> { error?: string | ReactElement | false; inputType: 'day' | 'month' | 'year'; } +export type IndividualDateInputProps = IndividualDateInputElementProps & + Omit< + FormElementProps, + 'error' | 'fieldsetProps' | 'legend' | 'legendProps' + >; + const labels: Record<'day' | 'month' | 'year', string> = { day: 'Day', month: 'Month', diff --git a/src/components/form-elements/index.ts b/src/components/form-elements/index.ts index 23b427ff..46a3cde8 100644 --- a/src/components/form-elements/index.ts +++ b/src/components/form-elements/index.ts @@ -9,6 +9,7 @@ export * from './form/index.js'; export * from './hint-text/index.js'; export * from './label/index.js'; export * from './legend/index.js'; +export * from './password-input/index.js'; export * from './radios/index.js'; export * from './select/index.js'; export * from './text-input/index.js'; diff --git a/src/components/form-elements/password-input/PasswordInput.tsx b/src/components/form-elements/password-input/PasswordInput.tsx new file mode 100644 index 00000000..4c4ee1d8 --- /dev/null +++ b/src/components/form-elements/password-input/PasswordInput.tsx @@ -0,0 +1,117 @@ +'use client'; + +import classNames from 'classnames'; +import { type PasswordInput as PasswordInputModule } from 'nhsuk-frontend'; +import { + forwardRef, + useEffect, + useImperativeHandle, + useRef, + useState, + type ComponentPropsWithoutRef, + type ComponentPropsWithRef, + type FC, +} from 'react'; + +import { Button } from '#components/form-elements/button/index.js'; +import { FormGroup } from '#components/utils/index.js'; +import { type FormElementProps } from '#util/types/FormTypes.js'; +import { type InputWidth } from '#util/types/NHSUKTypes.js'; + +export interface PasswordInputElementProps extends ComponentPropsWithoutRef<'input'> { + width?: InputWidth; + showPasswordText?: string; + showPasswordAriaLabelText?: string; + buttonProps?: ComponentPropsWithRef<'button'>; +} + +export type PasswordInputProps = PasswordInputElementProps & + Omit< + FormElementProps, + 'fieldsetProps' | 'legend' | 'legendProps' + >; + +const PasswordInputButton: FC> = ({ + children, + className, + ...rest +}) => ( + +); + +export const PasswordInput = forwardRef( + ({ buttonProps, formGroupProps, ...props }, forwardedRef) => { + const { showPasswordText, showPasswordAriaLabelText, ...rest } = props; + + const moduleRef = useRef(null); + const importRef = useRef>(null); + const [instanceError, setInstanceError] = useState(); + const [instance, setInstance] = useState(); + + useImperativeHandle(formGroupProps?.ref, () => moduleRef.current!, [moduleRef]); + + useEffect(() => { + if (!moduleRef.current || importRef.current || instance) { + return; + } + + importRef.current = import('nhsuk-frontend') + .then(({ PasswordInput }) => setInstance(new PasswordInput(moduleRef.current))) + .catch(setInstanceError); + }, [moduleRef, importRef, instance]); + + if (instanceError) { + throw instanceError; + } + + return ( + + {...rest} + formGroupProps={{ + ...formGroupProps, + 'className': classNames('nhsuk-password-input', formGroupProps?.className), + 'data-module': 'nhsuk-password-input', + 'afterInput': ({ id }) => ( + + {showPasswordText ?? 'Show'} + + ), + 'ref': moduleRef, + }} + > + {({ width, className, error, autoComplete, ...rest }) => ( + + )} + + ); + }, +); + +PasswordInput.displayName = 'PasswordInput'; diff --git a/src/components/form-elements/password-input/__tests__/PasswordInput.test.tsx b/src/components/form-elements/password-input/__tests__/PasswordInput.test.tsx new file mode 100644 index 00000000..fd182053 --- /dev/null +++ b/src/components/form-elements/password-input/__tests__/PasswordInput.test.tsx @@ -0,0 +1,127 @@ +import { createRef } from 'react'; + +import { PasswordInput } from '..'; + +import { renderClient, renderServer } from '#util/components'; +import { type InputWidth } from '#util/types'; + +describe('PasswordInput', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('matches snapshot', async () => { + const { container } = await renderClient( + , + { moduleName: 'nhsuk-password-input' }, + ); + + expect(container).toMatchSnapshot('PasswordInput'); + }); + + it('matches snapshot (via server)', async () => { + const { container, element } = await renderServer( + , + { moduleName: 'nhsuk-password-input' }, + ); + + expect(container).toMatchSnapshot('server'); + + await renderClient(element, { + moduleName: 'nhsuk-password-input', + hydrate: true, + container, + }); + + expect(container).toMatchSnapshot('client'); + }); + + it('forwards refs', async () => { + const groupRef = createRef(); + const fieldRef = createRef(); + const buttonRef = createRef(); + + const { container } = await renderClient( + , + { className: 'nhsuk-password-input' }, + ); + + const groupEl = container.querySelector('div'); + const inputEl = container.querySelector('input'); + const buttonEl = container.querySelector('button'); + + expect(groupRef.current).toBe(groupEl); + expect(groupRef.current).toHaveClass('nhsuk-form-group'); + + expect(fieldRef.current).toBe(inputEl); + expect(fieldRef.current).toHaveClass('nhsuk-input'); + + expect(buttonRef.current).toBe(buttonEl); + expect(buttonRef.current).toHaveClass('nhsuk-password-input__toggle'); + }); + + it('should handle DOM events where ref exists', async () => { + const ref = createRef(); + const mock = jest.fn(); + + const handleClick = () => { + if (!ref.current) return; + mock(); + }; + + const { modules } = await renderClient(, { + className: 'nhsuk-input', + }); + + const [inputEl] = modules; + inputEl.click(); + + expect(mock).toHaveBeenCalledTimes(1); + }); + + it.each([undefined, '5', '10'])( + 'sets the provided input width if specified with %s', + async (width) => { + const { modules } = await renderClient(, { + className: 'nhsuk-input', + }); + + const [inputEl] = modules; + + if (width) { + expect(inputEl).toHaveClass(`nhsuk-input--width-${width}`); + } else { + expect(inputEl.className.indexOf('nhsuk-input--width')).toBe(-1); + } + }, + ); + + it('sets the error class when error message is provided', async () => { + const { modules } = await renderClient( + <> + + + , + { className: 'nhsuk-input' }, + ); + + const [inputEl1, inputEl2] = modules; + + expect(inputEl1).not.toHaveClass('nhsuk-input--error'); + expect(inputEl2).toHaveClass('nhsuk-input--error'); + }); + + it('sets the provided input width if specified', async () => { + const { modules } = await renderClient(, { + className: 'nhsuk-input', + }); + + const [inputEl] = modules; + + expect(inputEl).toHaveClass('nhsuk-input--width-5'); + }); +}); diff --git a/src/components/form-elements/password-input/__tests__/__snapshots__/PasswordInput.test.tsx.snap b/src/components/form-elements/password-input/__tests__/__snapshots__/PasswordInput.test.tsx.snap new file mode 100644 index 00000000..595624c9 --- /dev/null +++ b/src/components/form-elements/password-input/__tests__/__snapshots__/PasswordInput.test.tsx.snap @@ -0,0 +1,131 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`PasswordInput matches snapshot (via server): client 1`] = ` +
+
+ +
+ +
+ +
+
+
+`; + +exports[`PasswordInput matches snapshot (via server): server 1`] = ` +
+
+ +
+ + +
+
+
+`; + +exports[`PasswordInput matches snapshot: PasswordInput 1`] = ` +
+
+ +
+ +
+ +
+
+
+`; diff --git a/src/components/form-elements/password-input/index.ts b/src/components/form-elements/password-input/index.ts new file mode 100644 index 00000000..8452522a --- /dev/null +++ b/src/components/form-elements/password-input/index.ts @@ -0,0 +1 @@ +export * from './PasswordInput.js'; diff --git a/src/components/form-elements/radios/Radios.tsx b/src/components/form-elements/radios/Radios.tsx index bc77d4e5..62398ab6 100644 --- a/src/components/form-elements/radios/Radios.tsx +++ b/src/components/form-elements/radios/Radios.tsx @@ -18,13 +18,15 @@ import { FormGroup } from '#components/utils/index.js'; import { generateRandomName } from '#util/tools/index.js'; import { type FormElementProps } from '#util/types/FormTypes.js'; -export interface RadiosProps - extends ComponentPropsWithoutRef<'div'>, Omit { +export interface RadiosElementProps extends ComponentPropsWithoutRef<'div'> { idPrefix?: string; inline?: boolean; small?: boolean; } +export type RadiosProps = RadiosElementProps & + Omit, 'label' | 'labelProps'>; + const RadiosComponent = forwardRef((props, forwardedRef) => { const { children, idPrefix, ...rest } = props; @@ -89,7 +91,7 @@ const RadiosComponent = forwardRef((props, forwarde } return ( - inputType="radios" {...rest}> + inputType="radios" {...rest}> {({ className, inline, small, name, id, error, ...restRenderProps }) => { resetRadioIds(); const contextValue: IRadiosContext = { diff --git a/src/components/form-elements/radios/components/RadiosItem.tsx b/src/components/form-elements/radios/components/RadiosItem.tsx index 7dfff80a..ceb66888 100644 --- a/src/components/form-elements/radios/components/RadiosItem.tsx +++ b/src/components/form-elements/radios/components/RadiosItem.tsx @@ -17,15 +17,18 @@ import { HintText } from '#components/form-elements/hint-text/index.js'; import { Label } from '#components/form-elements/label/index.js'; import { type FormElementProps } from '#util/types/FormTypes.js'; -export interface RadiosItemProps - extends - ComponentPropsWithoutRef<'input'>, - Pick { +export interface RadiosItemElementProps extends ComponentPropsWithoutRef<'input'> { conditional?: ReactNode; forceShowConditional?: boolean; conditionalProps?: ComponentPropsWithRef<'div'>; } +export type RadiosItemProps = RadiosItemElementProps & + Omit< + FormElementProps, + 'fieldsetProps' | 'label' | 'legend' | 'legendProps' + >; + export const RadiosItem = forwardRef((props, forwardedRef) => { const { className, diff --git a/src/components/form-elements/select/Select.tsx b/src/components/form-elements/select/Select.tsx index dbd47b53..eaaff3ad 100644 --- a/src/components/form-elements/select/Select.tsx +++ b/src/components/form-elements/select/Select.tsx @@ -1,17 +1,19 @@ 'use client'; import classNames from 'classnames'; -import { forwardRef, type ComponentPropsWithoutRef } from 'react'; +import { forwardRef, type ComponentPropsWithoutRef, type FC } from 'react'; import { FormGroup } from '#components/utils/index.js'; import { type FormElementProps } from '#util/types/FormTypes.js'; -export type SelectProps = ComponentPropsWithoutRef<'select'> & - Omit; +export type SelectElementProps = ComponentPropsWithoutRef<'select'>; + +export type SelectProps = SelectElementProps & + Omit, 'fieldsetProps' | 'legend' | 'legendProps'>; const SelectComponent = forwardRef( ({ children, ...rest }, forwardedRef) => ( - inputType="select" {...rest}> + inputType="select" {...rest}> {({ className, error, ...restRenderProps }) => ( ((props, fo type={type} {...rest} /> - ); - - return prefix || suffix ? ( -
- {prefix ? : null} - {Input} - {suffix ? : null} -
- ) : ( - Input - ); - }} - -)); + )} + + ), +); TextInput.displayName = 'TextInput'; diff --git a/src/components/form-elements/text-input/__tests__/TextInput.test.tsx b/src/components/form-elements/text-input/__tests__/TextInput.test.tsx index 450573e6..b5ffc1df 100644 --- a/src/components/form-elements/text-input/__tests__/TextInput.test.tsx +++ b/src/components/form-elements/text-input/__tests__/TextInput.test.tsx @@ -149,8 +149,8 @@ describe('TextInput', () => { ({ prefix, suffix }) => { const { container } = render(); - const prefixElement = container.querySelector('.nhsuk-input__wrapper > .nhsuk-input__prefix'); - const suffixElement = container.querySelector('.nhsuk-input__wrapper > .nhsuk-input__suffix'); + const prefixElement = container.querySelector('.nhsuk-input-wrapper > .nhsuk-input__prefix'); + const suffixElement = container.querySelector('.nhsuk-input-wrapper > .nhsuk-input__suffix'); if (prefix) { expect(prefixElement).not.toBeNull(); @@ -167,10 +167,10 @@ describe('TextInput', () => { } if (!prefix && !suffix) { - expect(container.querySelector('.nhsuk-input__wrapper')).toBeNull(); + expect(container.querySelector('.nhsuk-input-wrapper')).toBeNull(); expect(container.querySelector('.nhsuk-input')).not.toBeNull(); } else { - expect(container.querySelector('.nhsuk-input__wrapper > .nhsuk-input')).not.toBeNull(); + expect(container.querySelector('.nhsuk-input-wrapper > .nhsuk-input')).not.toBeNull(); } }, ); diff --git a/src/components/form-elements/textarea/Textarea.tsx b/src/components/form-elements/textarea/Textarea.tsx index 1a47453c..de4cc214 100644 --- a/src/components/form-elements/textarea/Textarea.tsx +++ b/src/components/form-elements/textarea/Textarea.tsx @@ -6,11 +6,16 @@ import { forwardRef, type ComponentPropsWithoutRef } from 'react'; import { FormGroup } from '#components/utils/index.js'; import { type FormElementProps } from '#util/types/FormTypes.js'; -export type TextareaProps = ComponentPropsWithoutRef<'textarea'> & - Omit; +export type TextareaElementProps = ComponentPropsWithoutRef<'textarea'>; + +export type TextareaProps = TextareaElementProps & + Omit< + FormElementProps, + 'fieldsetProps' | 'legend' | 'legendProps' + >; export const Textarea = forwardRef((props, forwardedRef) => ( - inputType="textarea" {...props}> + inputType="textarea" {...props}> {({ children, className, error, ...rest }) => ( <>