Merged in feat/3685-new-textarea-component (pull request #3392)
feat(SW-3685): Add new TextArea and FormTextArea components * Add new TextArea and FormTextArea components * Update example form with description * Merge branch 'master' into feat/3685-new-textarea-component * Formatting new files with new prettier config * Added custom controls for the text area story Approved-by: Linus Flood
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
"use client"
|
||||
|
||||
import { forwardRef } from "react"
|
||||
import { Text, TextField } from "react-aria-components"
|
||||
import { Controller, useFormContext } from "react-hook-form"
|
||||
import { useIntl, type IntlShape } from "react-intl"
|
||||
import { cx } from "class-variance-authority"
|
||||
|
||||
import { Error } from "../ErrorMessage/Error"
|
||||
import { mergeRefs } from "../utils/mergeRefs"
|
||||
import { TextArea } from "../../TextArea"
|
||||
|
||||
import styles from "./textarea.module.css"
|
||||
|
||||
import type { FormTextAreaProps } from "./textarea"
|
||||
import { MaterialIcon, MaterialIconProps } from "../../Icons/MaterialIcon"
|
||||
|
||||
const defaultErrorFormatter = (
|
||||
_intl: IntlShape,
|
||||
errorMessage?: string
|
||||
): string => errorMessage ?? ""
|
||||
|
||||
export const FormTextArea = forwardRef<HTMLTextAreaElement, FormTextAreaProps>(
|
||||
function FormTextArea(
|
||||
{
|
||||
className = "",
|
||||
description = "",
|
||||
descriptionIcon = "info" as MaterialIconProps["icon"],
|
||||
disabled = false,
|
||||
errorFormatter,
|
||||
hideError,
|
||||
label,
|
||||
name,
|
||||
placeholder,
|
||||
readOnly = false,
|
||||
registerOptions = {},
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) {
|
||||
const intl = useIntl()
|
||||
const { control } = useFormContext()
|
||||
|
||||
const formatErrorMessage = errorFormatter ?? defaultErrorFormatter
|
||||
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
rules={registerOptions}
|
||||
render={({ field, fieldState }) => {
|
||||
const isDisabled = disabled || field.disabled
|
||||
const hasError = fieldState.invalid && !hideError
|
||||
const showDescription = description && !fieldState.error
|
||||
|
||||
return (
|
||||
<TextField
|
||||
className={cx(styles.wrapper, className)}
|
||||
isDisabled={isDisabled}
|
||||
isReadOnly={readOnly}
|
||||
isInvalid={fieldState.invalid}
|
||||
isRequired={!!registerOptions.required}
|
||||
>
|
||||
<TextArea
|
||||
{...props}
|
||||
ref={mergeRefs(field.ref, ref)}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
onChange={field.onChange}
|
||||
value={field.value ?? ""}
|
||||
id={field.name}
|
||||
label={label}
|
||||
placeholder={placeholder}
|
||||
readOnly={readOnly}
|
||||
disabled={isDisabled}
|
||||
required={!!registerOptions.required}
|
||||
/>
|
||||
{showDescription ? (
|
||||
<Text className={styles.description} slot="description">
|
||||
<MaterialIcon icon={descriptionIcon} size={20} />
|
||||
{description}
|
||||
</Text>
|
||||
) : null}
|
||||
{hasError && fieldState.error ? (
|
||||
<Text slot="errorMessage" aria-live="polite">
|
||||
<Error>
|
||||
{formatErrorMessage(intl, fieldState.error.message)}
|
||||
</Error>
|
||||
</Text>
|
||||
) : null}
|
||||
</TextField>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
FormTextArea.displayName = "FormTextArea"
|
||||
|
||||
// Default export for backwards compatibility
|
||||
export default FormTextArea
|
||||
@@ -0,0 +1,16 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.description {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--Space-x05);
|
||||
margin-top: var(--Space-x1);
|
||||
font-size: var(--Body-Supporting-text-Size);
|
||||
font-family: var(--Body-Supporting-text-Font-family, "Fira Sans");
|
||||
font-style: normal;
|
||||
font-weight: var(--Body-Supporting-text-Font-weight);
|
||||
letter-spacing: var(--Body-Supporting-text-Letter-spacing);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { RegisterOptions } from "react-hook-form"
|
||||
import type { IntlShape } from "react-intl"
|
||||
|
||||
import type { TextAreaProps } from "../../TextArea/types"
|
||||
import { MaterialIconProps } from "../../Icons/MaterialIcon"
|
||||
|
||||
export interface FormTextAreaProps extends TextAreaProps {
|
||||
/** Helper text displayed below the textarea (hidden when there's an error) */
|
||||
description?: string
|
||||
/** Icon to display with the description text. Defaults to 'info' */
|
||||
descriptionIcon?: MaterialIconProps["icon"]
|
||||
/** Field name for react-hook-form registration */
|
||||
name: string
|
||||
/** react-hook-form validation rules */
|
||||
registerOptions?: RegisterOptions
|
||||
/** Hide the error message (useful when showing errors elsewhere) */
|
||||
hideError?: boolean
|
||||
/** Custom formatter for error messages with i18n support */
|
||||
errorFormatter?: (intl: IntlShape, errorMessage?: string) => string
|
||||
}
|
||||
Reference in New Issue
Block a user