From 87a2aa6d240b8e535328d930e781c44aa9858832 Mon Sep 17 00:00:00 2001 From: Tamas Kovacs Date: Tue, 2 Dec 2025 16:08:07 +0100 Subject: [PATCH] feat(many): rework TextArea and dependent FormField components INSTUI-4812 --- docs/guides/form-errors.md | 16 +- docs/guides/upgrade-guide.md | 56 ++ .../summaries-for-llms-file.json | 2 +- packages/ui-checkbox/src/Checkbox/index.tsx | 8 +- .../src/DateTimeInput/index.tsx | 10 +- packages/ui-form-field/package.json | 1 + .../ui-form-field/src/FormField/README.md | 8 +- .../ui-form-field/src/FormField/index.tsx | 10 +- packages/ui-form-field/src/FormField/props.ts | 17 +- .../src/FormFieldGroup/index.tsx | 23 +- .../ui-form-field/src/FormFieldGroup/props.ts | 18 +- .../src/FormFieldGroup/styles.ts | 15 +- .../src/FormFieldLayout/index.tsx | 330 ++++----- .../src/FormFieldLayout/props.ts | 34 +- .../src/FormFieldLayout/styles.ts | 57 +- .../src/FormFieldLayout/theme.ts | 59 -- .../src/FormFieldMessage/index.tsx | 82 +-- .../src/FormFieldMessage/props.ts | 10 +- .../src/FormFieldMessage/styles.ts | 27 +- .../src/FormFieldMessage/theme.ts | 60 -- .../src/FormFieldMessages/styles.ts | 3 +- packages/ui-form-field/tsconfig.build.json | 1 + .../ui-number-input/src/NumberInput/index.tsx | 20 +- .../ui-number-input/src/NumberInput/props.ts | 1 - .../ui-number-input/src/NumberInput/styles.ts | 4 - .../src/RadioInputGroup/README.md | 2 +- .../__tests__/RadioInputGroup.test.tsx | 2 +- .../src/RadioInputGroup/index.tsx | 31 +- .../src/RadioInputGroup/props.ts | 10 +- .../src/RadioInputGroup/styles.ts | 46 -- .../src/RadioInputGroup/theme.ts | 45 -- .../src/TextArea/__tests__/TextArea.test.tsx | 13 +- packages/ui-text-area/src/TextArea/index.tsx | 626 +++++++++--------- packages/ui-text-area/src/TextArea/props.ts | 15 +- packages/ui-text-area/src/TextArea/styles.ts | 125 ++-- .../ui-text-input/src/TextInput/index.tsx | 24 +- packages/ui-text-input/src/TextInput/props.ts | 7 +- .../ui-text-input/src/TextInput/styles.ts | 3 - pnpm-lock.yaml | 3 + 39 files changed, 865 insertions(+), 959 deletions(-) delete mode 100644 packages/ui-form-field/src/FormFieldLayout/theme.ts delete mode 100644 packages/ui-form-field/src/FormFieldMessage/theme.ts delete mode 100644 packages/ui-radio-input/src/RadioInputGroup/styles.ts delete mode 100644 packages/ui-radio-input/src/RadioInputGroup/theme.ts diff --git a/docs/guides/form-errors.md b/docs/guides/form-errors.md index 250c63a685..e11b1e3010 100644 --- a/docs/guides/form-errors.md +++ b/docs/guides/form-errors.md @@ -15,7 +15,6 @@ type: code --- type FormMessages = { type: - | 'newError' | 'error' | 'hint' | 'success' @@ -33,7 +32,7 @@ type: example const PasswordExample = () => { const [password, setPassword] = useState('') const messages = password.length < 6 - ? [{type: 'newError', text: 'Password have to be at least 6 characters long!'}] + ? [{type: 'error', text: 'Password have to be at least 6 characters long!'}] : [] return ( { render() ``` -However you might have noticed from the type definition that a message can be `error` and `newError` type. This is due to compatibility reasons. `error` is the older type and does not meet accessibility requirements, `newError` (hance the name) is the newer and more accessible format. +The `error` type has been updated to meet accessibility requirements with proper icons and visual styling. Previously, there was a `newError` type that provided this enhanced behavior, but it has been consolidated into the standard `error` type for consistency. `newError` has been deprecated. -We wanted to allow users to start using the new format without making it mandatory, but after the introductory period `newError` will be deprecated and `error` type will be changed to look and behave the same way. - -With this update we also introduced the "required asterisk" which will display an `*` character next to field labels that are required. This update is not opt-in and will apply to **all** InstUI form components so if you were relying on a custom solution for this feature before, you need to remove that to avoid having double asterisks. +We also introduced the "required asterisk" which displays an `*` character next to field labels that are required. This update applies to **all** InstUI form components, so if you were relying on a custom solution for this feature before, you need to remove that to avoid having double asterisks. Here are examples with different form components: @@ -62,17 +59,15 @@ type: example --- const Example = () => { const [showError, setShowError] = useState(true) - const [showNewError, setShowNewError] = useState(true) const [showLongError, setShowLongError] = useState(false) const [isRequired, setIsRequired] = useState(true) const messages = showError - ? [{type: showNewError ? 'newError' : 'error', text: showLongError ? 'Long error. Lorem ipsum dolor sit amet consectetur adipisicing elit. Dignissimos voluptas, esse commodi eos facilis voluptatibus harum exercitationem. Et magni est consectetur, eveniet veniam unde! Molestiae labore libero sapiente ad ratione.' : 'Short error message'}] + ? [{type: 'error', text: showLongError ? 'Long error. Lorem ipsum dolor sit amet consectetur adipisicing elit. Dignissimos voluptas, esse commodi eos facilis voluptatibus harum exercitationem. Et magni est consectetur, eveniet veniam unde! Molestiae labore libero sapiente ad ratione.' : 'Short error message'}] : [] const handleSettingsChange = (v) => { setShowError(v.includes('showError')) - setShowNewError(v.includes('showNewError')) setShowLongError(v.includes('showLongError')) setIsRequired(v.includes('isRequired')) } @@ -83,10 +78,9 @@ const Example = () => { name="errorOptions" description="Error message options" onChange={handleSettingsChange} - defaultValue={['showError', 'showNewError', 'isRequired']} + defaultValue={['showError', 'isRequired']} > - diff --git a/docs/guides/upgrade-guide.md b/docs/guides/upgrade-guide.md index eed6f8fac1..c8ae81c1dd 100644 --- a/docs/guides/upgrade-guide.md +++ b/docs/guides/upgrade-guide.md @@ -60,6 +60,62 @@ The new icons automatically sync with theme changes, support all InstUI color to - theme variable `lineHeight` is now removed. +### FormFieldGroup + +- theme variable `errorBorderColor` is now removed +- theme variable `errorFieldsPaddin` is now removed + +### FormFieldLayout + +- theme variable `spacing` is now removed +- theme variable `color` has been renamed to `textColor` +- theme variable `inlinePadding` is now removed +- theme variable `asteriskColor` is now removed + +### FormFieldMessage + +- theme variable `colorHint` has been renamed to `hintTextColor` +- theme variable `colorError` has been renamed to `errorTextColor` +- theme variable `colorSuccess` has been renamed to `successTextColor` +- theme variable `errorIconMarginRight` is now removed + +### FormFieldMessages + +- theme variable `topMargin` is now removed + +### TextArea + +- theme variable `smallFontSize` is now renamed to `fontSizeSm` +- theme variable `mediumFontSize` is now renamed to `fontSizeMd` +- theme variable `largeFontMedium` is now renamed to `fontSizeLg` +- theme variable `requiredInvalidColor` is now removed +- theme variable `borderStyle` is now removed +- theme variable `borderTopColor` is now removed +- theme variable `borderBottomColor` is now removed +- theme variable `borderLeftColor` is now removed +- theme variable `borderRightColor` is now removed +- theme variable `color` is now renamed to `textColor` +- theme variable `background` is now renamed to `backgroundColor` +- theme variable `focusOutlineWidth` is now removed +- theme variable `focusOutlineStyle` is now removed +- theme variable `focusOutlineColor` is now removed +- `error` or `success` messages are no longer displayed when the component is '`readOnly` or `disabled` + +### NumberInput + +- theme variable `requiredInvalidColor` is now removed +- `error` or `success` messages are no longer displayed when the component is '`readOnly` or `disabled` + +### RadioInputGroup + +- theme variable `invalidAsteriskColor` is now removed +- `error` or `success` messages are no longer displayed when the component is '`readOnly` or `disabled` + +### TextInput + +- theme variable `requiredInvalidColor` is now removed +- `error` or `success` messages are no longer displayed when the component is '`readOnly` or `disabled` + ## Codemods To ease the upgrade, we provide codemods that will automate most of the changes. Pay close attention to its output, it cannot refactor complex code! The codemod scripts can be run via the following commands: diff --git a/packages/__docs__/buildScripts/ai-accessible-documentation/summaries-for-llms-file.json b/packages/__docs__/buildScripts/ai-accessible-documentation/summaries-for-llms-file.json index 0fe03b9bed..5a3b891137 100644 --- a/packages/__docs__/buildScripts/ai-accessible-documentation/summaries-for-llms-file.json +++ b/packages/__docs__/buildScripts/ai-accessible-documentation/summaries-for-llms-file.json @@ -434,7 +434,7 @@ }, { "title": "form-errors", - "summary": "InstUI form components use a `messages` prop for error/hint/success messages. Supports both `error` (legacy) and `newError` (accessible) types. Required fields now show an asterisk automatically. Examples provided for various form components like TextInput, Checkbox, and DateTimeInput." + "summary": "InstUI form components use a `messages` prop for error/hint/success messages. Required fields now show an asterisk automatically. Examples provided for various form components like TextInput, Checkbox, and DateTimeInput." }, { "title": "layout-spacing", diff --git a/packages/ui-checkbox/src/Checkbox/index.tsx b/packages/ui-checkbox/src/Checkbox/index.tsx index f5c9798f9e..13f2740cce 100644 --- a/packages/ui-checkbox/src/Checkbox/index.tsx +++ b/packages/ui-checkbox/src/Checkbox/index.tsx @@ -185,8 +185,10 @@ class Checkbox extends Component { return isActiveElement(this._input) } - get isNewError() { - return !!this.props.messages?.find((m) => m.type === 'newError') + get isError() { + return !!this.props.messages?.find( + (m) => m.type === 'error' || m.type === 'newError' + ) } get invalid() { @@ -278,7 +280,7 @@ class Checkbox extends Component { display="block" margin="small 0 0" css={ - this.isNewError && + this.isError && (variant === 'toggle' ? styles?.indentedToggleError : styles?.indentedError) diff --git a/packages/ui-date-time-input/src/DateTimeInput/index.tsx b/packages/ui-date-time-input/src/DateTimeInput/index.tsx index 42fd8d3d7e..26dafaafd3 100644 --- a/packages/ui-date-time-input/src/DateTimeInput/index.tsx +++ b/packages/ui-date-time-input/src/DateTimeInput/index.tsx @@ -162,7 +162,7 @@ class DateTimeInput extends Component { ? this.props.invalidDateTimeMessage(parsed.toISOString(true)) : this.props.invalidDateTimeMessage } - errorMsg = text ? { text, type: 'newError' } : undefined + errorMsg = text ? { text, type: 'error' } : undefined return { iso: parsed.clone(), calendarSelectedDate: parsed.clone(), @@ -345,7 +345,7 @@ class DateTimeInput extends Component { ? this.props.invalidDateTimeMessage(dateStr ? dateStr : '') : this.props.invalidDateTimeMessage // eslint-disable-next-line no-param-reassign - newState.message = { text: text, type: 'newError' } + newState.message = { text: text, type: 'error' } } if (this.areDifferentDates(this.state.iso, newState.iso)) { if (typeof this.props.onChange === 'function') { @@ -569,10 +569,12 @@ class DateTimeInput extends Component { ...(messages || []) ] - const hasError = allMessages.find((m) => m.type === 'newError') + const hasError = allMessages.find( + (m) => m.type === 'newError' || m.type === 'error' + ) // if the component is in error state, create an empty error message to pass down to the subcomponents (DateInput and TimeInput) so they get a red outline and red required asterisk const subComponentMessages: FormMessage[] = hasError - ? [{ type: 'newError', text: '' }] + ? [{ type: 'error', text: '' }] : [] return ( diff --git a/packages/ui-form-field/package.json b/packages/ui-form-field/package.json index c28f0f8b94..5b0e029d49 100644 --- a/packages/ui-form-field/package.json +++ b/packages/ui-form-field/package.json @@ -39,6 +39,7 @@ "@instructure/ui-a11y-utils": "workspace:*", "@instructure/ui-grid": "workspace:*", "@instructure/ui-icons": "workspace:*", + "@instructure/ui-icons-lucide": "workspace:*", "@instructure/ui-react-utils": "workspace:*", "@instructure/ui-utils": "workspace:*", "@instructure/uid": "workspace:*" diff --git a/packages/ui-form-field/src/FormField/README.md b/packages/ui-form-field/src/FormField/README.md index eb7802a677..a6d6648734 100644 --- a/packages/ui-form-field/src/FormField/README.md +++ b/packages/ui-form-field/src/FormField/README.md @@ -11,25 +11,25 @@ type: example ---
+ messages={[{type:'success', text: 'This is a success message'}, {type:'error', text: 'An error message. It will wrap if the text is longer than the width of the container.'}]}> test
+ messages={[{type:'success', text: 'This is a success message'}, {type:'error', text: 'An error message. It will wrap if the text is longer than the width of the container.'}]}> test
+ messages={[{type:'success', text: 'success!'}, {type:'error', text: 'An error message. It will wrap if the text is longer than the width of the container.'}]}> test
+ messages={[{type:'success', text: 'success!'}, {type:'error', text: 'An error message. It will wrap if the text is longer than the width of the container.'}]}> test diff --git a/packages/ui-form-field/src/FormField/index.tsx b/packages/ui-form-field/src/FormField/index.tsx index 9f1949a0d5..6d41f551ef 100644 --- a/packages/ui-form-field/src/FormField/index.tsx +++ b/packages/ui-form-field/src/FormField/index.tsx @@ -26,7 +26,10 @@ import { Component } from 'react' import { omitProps, pickProps } from '@instructure/ui-react-utils' -import { FormFieldLayout } from '../FormFieldLayout' +import { + FormFieldLayout, + allowedProps as formFieldLayoutAllowedProps +} from '../FormFieldLayout' import { allowedProps } from './props' import type { FormFieldProps } from './props' @@ -63,7 +66,7 @@ class FormField extends Component { return ( { htmlFor={this.props.id} elementRef={this.handleRef} margin={this.props.margin} + isRequired={this.props.isRequired} + disabled={this.props.disabled} + readOnly={this.props.readOnly} /> ) } diff --git a/packages/ui-form-field/src/FormField/props.ts b/packages/ui-form-field/src/FormField/props.ts index 2522ffc667..f5e3c1b491 100644 --- a/packages/ui-form-field/src/FormField/props.ts +++ b/packages/ui-form-field/src/FormField/props.ts @@ -57,11 +57,23 @@ type FormFieldOwnProps = { * provides a reference to the underlying html root element */ elementRef?: (element: Element | null) => void + /** + * If `true`, displays an asterisk after the label to indicate the field is required + */ + isRequired?: boolean /** * Margin around the component. Accepts a `Spacing` token. See token values and example usage in [this guide](https://instructure.design/#layout-spacing). */ margin?: Spacing + /** + * Whether the field is disabled. When true, error and success messages will be hidden. + */ + disabled?: boolean + /** + * Whether the field is read-only. When true, error and success messages will be hidden. + */ + readOnly?: boolean } type PropKeys = keyof FormFieldOwnProps @@ -82,7 +94,10 @@ const allowedProps: AllowedPropKeys = [ 'width', 'inputContainerRef', 'elementRef', - 'margin' + 'isRequired', + 'margin', + 'disabled', + 'readOnly' ] export type { FormFieldOwnProps, FormFieldProps } diff --git a/packages/ui-form-field/src/FormFieldGroup/index.tsx b/packages/ui-form-field/src/FormFieldGroup/index.tsx index cb06ec6e44..520a6f959a 100644 --- a/packages/ui-form-field/src/FormFieldGroup/index.tsx +++ b/packages/ui-form-field/src/FormFieldGroup/index.tsx @@ -28,13 +28,16 @@ import { Grid } from '@instructure/ui-grid' import { pickProps, omitProps } from '@instructure/ui-react-utils' import { withStyleRework as withStyle } from '@instructure/emotion' -import { FormFieldLayout } from '../FormFieldLayout' +import { + FormFieldLayout, + allowedProps as formFieldLayoutAllowedProps +} from '../FormFieldLayout' import generateStyle from './styles' import generateComponentTheme from './theme' import { allowedProps } from './props' -import type { FormFieldGroupProps, FormFieldGroupStyleProps } from './props' +import type { FormFieldGroupProps } from './props' /** --- @@ -68,21 +71,11 @@ class FormFieldGroup extends Component { } componentDidMount() { - this.props.makeStyles?.(this.makeStylesVariables) + this.props.makeStyles?.() } componentDidUpdate() { - this.props.makeStyles?.(this.makeStylesVariables) - } - - get makeStylesVariables(): FormFieldGroupStyleProps { - // new form errors dont need borders - const oldInvalid = - !!this.props.messages && - this.props.messages.findIndex((message) => { - return message.type === 'error' - }) >= 0 - return { invalid: oldInvalid } + this.props.makeStyles?.() } get invalid() { @@ -172,7 +165,7 @@ class FormFieldGroup extends Component { return ( void } -type FormFieldGroupStyleProps = { - invalid: boolean -} - type PropKeys = keyof FormFieldGroupOwnProps type AllowedPropKeys = Readonly> @@ -86,6 +89,7 @@ const allowedProps: AllowedPropKeys = [ 'messages', 'messagesId', 'disabled', + 'readOnly', 'children', 'layout', 'rowSpacing', @@ -95,9 +99,5 @@ const allowedProps: AllowedPropKeys = [ 'elementRef' ] -export type { - FormFieldGroupProps, - FormFieldGroupStyleProps, - FormFieldGroupStyle -} +export type { FormFieldGroupProps, FormFieldGroupStyle } export { allowedProps } diff --git a/packages/ui-form-field/src/FormFieldGroup/styles.ts b/packages/ui-form-field/src/FormFieldGroup/styles.ts index 2cd9af6cc6..4ab927cc5f 100644 --- a/packages/ui-form-field/src/FormFieldGroup/styles.ts +++ b/packages/ui-form-field/src/FormFieldGroup/styles.ts @@ -23,11 +23,7 @@ */ import type { FormFieldGroupTheme } from '@instructure/shared-types' -import type { - FormFieldGroupProps, - FormFieldGroupStyleProps, - FormFieldGroupStyle -} from './props' +import type { FormFieldGroupProps, FormFieldGroupStyle } from './props' /** * --- @@ -41,11 +37,9 @@ import type { */ const generateStyle = ( componentTheme: FormFieldGroupTheme, - props: FormFieldGroupProps, - state: FormFieldGroupStyleProps + props: FormFieldGroupProps ): FormFieldGroupStyle => { const { disabled } = props - const { invalid } = state return { formFieldGroup: { @@ -54,11 +48,6 @@ const generateStyle = ( borderRadius: componentTheme.borderRadius, display: 'block', - ...(invalid && { - borderColor: componentTheme.errorBorderColor, - padding: componentTheme.errorFieldsPadding - }), - ...(disabled && { opacity: 0.6, cursor: 'not-allowed', diff --git a/packages/ui-form-field/src/FormFieldLayout/index.tsx b/packages/ui-form-field/src/FormFieldLayout/index.tsx index 9c7a352137..242fe6e016 100644 --- a/packages/ui-form-field/src/FormFieldLayout/index.tsx +++ b/packages/ui-form-field/src/FormFieldLayout/index.tsx @@ -22,188 +22,216 @@ * SOFTWARE. */ -import { Component } from 'react' +import { forwardRef, useEffect, useState, useCallback } from 'react' import { hasVisibleChildren } from '@instructure/ui-a11y-utils' -import { - omitProps, - getElementType, - withDeterministicId -} from '@instructure/ui-react-utils' +import { omitProps, useDeterministicId } from '@instructure/ui-react-utils' -import { withStyleRework as withStyle } from '@instructure/emotion' +import { useStyle } from '@instructure/emotion' import { FormFieldMessages } from '../FormFieldMessages' import generateStyle from './styles' -import { allowedProps, FormFieldStyleProps } from './props' +import { allowedProps } from './props' import type { FormFieldLayoutProps } from './props' -import generateComponentTheme from './theme' /** --- parent: FormField --- **/ -@withDeterministicId() -@withStyle(generateStyle, generateComponentTheme) -class FormFieldLayout extends Component { - static readonly componentId = 'FormFieldLayout' - - static allowedProps = allowedProps - static defaultProps = { - inline: false, - layout: 'stacked', - as: 'label', - labelAlign: 'end' - } as const - - constructor(props: FormFieldLayoutProps) { - super(props) - this._messagesId = props.messagesId || props.deterministicId!() - this._labelId = props.deterministicId!('FormField-Label') - } - - private _messagesId: string - private _labelId: string - - ref: Element | null = null - - handleRef = (el: Element | null) => { - const { elementRef } = this.props - - this.ref = el - - if (typeof elementRef === 'function') { - elementRef(el) - } - } - - componentDidMount() { - this.props.makeStyles?.(this.makeStyleProps()) - } - - componentDidUpdate() { - this.props.makeStyles?.(this.makeStyleProps()) - } - - makeStyleProps = (): FormFieldStyleProps => { - const hasNewErrorMsgAndIsGroup = - !!this.props.messages?.find((m) => m.type === 'newError') && - !!this.props.isGroup - return { - hasMessages: this.hasMessages, - hasVisibleLabel: this.hasVisibleLabel, - // if true render error message above the controls (and below the label) - hasNewErrorMsgAndIsGroup: hasNewErrorMsgAndIsGroup - } - } - - get hasVisibleLabel() { - return this.props.label ? hasVisibleChildren(this.props.label) : false - } +const FormFieldLayout = forwardRef( + (props, ref) => { + const { + inline = false, + layout = 'stacked', + as = 'label', + labelAlign = 'end', + vAlign, + label, + messages, + messagesId: messagesIdProp, + children, + width, + elementRef, + inputContainerRef, + isGroup, + isRequired = false, + margin, + disabled = false, + readOnly = false, + themeOverride, + ...rest + } = props + + // Deterministic ID generation + const [deterministicId, setDeterministicId] = useState() + const getId = useDeterministicId('FormFieldLayout') + useEffect(() => { + setDeterministicId(getId()) + }, []) + + const messagesId = messagesIdProp || deterministicId + const labelId = deterministicId ? `${deterministicId}-Label` : undefined + + // Filter out error and success messages when disabled or readOnly + const filteredMessages = + disabled || readOnly + ? messages?.filter( + (msg) => + msg.type !== 'error' && + msg.type !== 'newError' && + msg.type !== 'success' + ) + : messages + + // Compute style props + const hasMessages = + filteredMessages && filteredMessages.length > 0 + ? filteredMessages.some((msg) => { + if (msg.text) { + if (typeof msg.text === 'string') { + return msg.text.length > 0 + } + return true + } + return false + }) + : false + + const hasVisibleLabel = label ? hasVisibleChildren(label) : false + + const hasErrorMsgAndIsGroup = + !!filteredMessages?.find( + (m) => m.type === 'error' || m.type === 'newError' + ) && !!isGroup + + const invalid = !!filteredMessages?.find( + (m) => m.type === 'error' || m.type === 'newError' + ) - get hasMessages() { - if (!this.props.messages || this.props.messages.length == 0) { - return false - } - for (const msg of this.props.messages) { - if (msg.text) { - if (typeof msg.text === 'string') { - return msg.text.length > 0 + // Styles + const styles = useStyle({ + generateStyle, + themeOverride, + params: { + hasMessages, + hasVisibleLabel, + hasErrorMsgAndIsGroup, + inline, + layout, + vAlign, + labelAlign, + margin, + messages: filteredMessages, + isRequired, + invalid + }, + componentId: 'FormFieldLayout', + displayName: 'FormFieldLayout' + }) + + const ElementType = as + + const handleRef = useCallback( + (el: Element | null) => { + if (typeof ref === 'function') { + ref(el) + } else if (ref) { + const refObject = ref as React.MutableRefObject + refObject.current = el } - // this is more complicated (e.g. an array, a Component,...) - // but we don't try to optimize here for these cases - return true - } - } - return false - } - get elementType() { - return getElementType(FormFieldLayout, this.props) - } + if (typeof elementRef === 'function') { + elementRef(el) + } + }, + [ref, elementRef] + ) - handleInputContainerRef = (node: HTMLElement | null) => { - if (typeof this.props.inputContainerRef === 'function') { - this.props.inputContainerRef(node) - } - } + const handleInputContainerRef = useCallback( + (node: HTMLElement | null) => { + if (typeof inputContainerRef === 'function') { + inputContainerRef(node) + } + }, + [inputContainerRef] + ) - renderLabel() { - if (this.hasVisibleLabel) { - if (this.elementType == 'fieldset') { - // `legend` has some special built in CSS, this can only be reset - // this way https://stackoverflow.com/a/65866981/319473 - return ( - - - {this.props.label} + const renderLabel = () => { + const labelContent = hasVisibleLabel ? ( + <> + {label} + {isRequired && ( + + {' '} + * - - ) - } - return ( - {this.props.label} + )} + + ) : ( + label ) - } else if (this.props.label) { - if (this.elementType == 'fieldset') { + + if (hasVisibleLabel) { + if (ElementType === 'fieldset') { + // `legend` has some special built in CSS, this can only be reset + // this way https://stackoverflow.com/a/65866981/319473 + return ( + + {labelContent} + + ) + } + return {labelContent} + } else if (label) { + if (ElementType === 'fieldset') { + return ( + + {label} + + ) + } + // needs to be wrapped because it needs an `id` return ( - - {this.props.label} - +
+ {label} +
) - } - // needs to be wrapped because it needs an `id` - return ( -
- {this.props.label} -
- ) - } else return null - } - - renderVisibleMessages() { - return this.hasMessages ? ( - - ) : null - } - - render() { - // Should be `