Skip to content

Commit 03b756d

Browse files
committed
feat(many): rework TextArea and dependent FormField components
INSTUI-4812
1 parent f5ef5ee commit 03b756d

File tree

38 files changed

+794
-899
lines changed

38 files changed

+794
-899
lines changed

docs/guides/form-errors.md

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ type: code
1515
---
1616
type FormMessages = {
1717
type:
18-
| 'newError'
1918
| 'error'
2019
| 'hint'
2120
| 'success'
@@ -33,7 +32,7 @@ type: example
3332
const PasswordExample = () => {
3433
const [password, setPassword] = useState('')
3534
const messages = password.length < 6
36-
? [{type: 'newError', text: 'Password have to be at least 6 characters long!'}]
35+
? [{type: 'error', text: 'Password have to be at least 6 characters long!'}]
3736
: []
3837
return (
3938
<TextInput
@@ -48,11 +47,9 @@ const PasswordExample = () => {
4847
render(<PasswordExample/>)
4948
```
5049

51-
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.
50+
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.
5251

53-
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.
54-
55-
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.
52+
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.
5653

5754
Here are examples with different form components:
5855

@@ -62,17 +59,15 @@ type: example
6259
---
6360
const Example = () => {
6461
const [showError, setShowError] = useState(true)
65-
const [showNewError, setShowNewError] = useState(true)
6662
const [showLongError, setShowLongError] = useState(false)
6763
const [isRequired, setIsRequired] = useState(true)
6864

6965
const messages = showError
70-
? [{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'}]
66+
? [{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'}]
7167
: []
7268

7369
const handleSettingsChange = (v) => {
7470
setShowError(v.includes('showError'))
75-
setShowNewError(v.includes('showNewError'))
7671
setShowLongError(v.includes('showLongError'))
7772
setIsRequired(v.includes('isRequired'))
7873
}
@@ -83,10 +78,9 @@ const Example = () => {
8378
name="errorOptions"
8479
description="Error message options"
8580
onChange={handleSettingsChange}
86-
defaultValue={['showError', 'showNewError', 'isRequired']}
81+
defaultValue={['showError', 'isRequired']}
8782
>
8883
<Checkbox label="Show error message" value="showError"/>
89-
<Checkbox label="Use the new error type" value="showNewError" />
9084
<Checkbox label="Use long message" value="showLongError" />
9185
<Checkbox label="Make fields required" value="isRequired" />
9286
</CheckboxGroup>

docs/guides/upgrade-guide.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,58 @@ The new icons automatically sync with theme changes, support all InstUI color to
5151
- `as` prop has been removed, `Spinner` will always render as a `<div>` element.
5252
- `elementRef` prop has been removed, use the `ref` prop instead.
5353

54+
### FormFieldGroup
55+
56+
- theme variable `errorBorderColor` is now removed
57+
- theme variable `errorFieldsPaddin` is now removed
58+
59+
### FormFieldLayout
60+
61+
- theme variable `spacing` is now removed
62+
- theme variable `color` has been renamed to `textColor`
63+
- theme variable `inlinePadding` is now removed
64+
- theme variable `asteriskColor` is now removed
65+
66+
### FormFieldMessage
67+
68+
- theme variable `colorHint` has been renamed to `hintTextColor`
69+
- theme variable `colorError` has been renamed to `errorTextColor`
70+
- theme variable `colorSuccess` has been renamed to `successTextColor`
71+
- theme variable `errorIconMarginRight` is now removed
72+
73+
### FormFieldMessages
74+
75+
- theme variable `topMargin` is now removed
76+
77+
### TextArea
78+
79+
- theme variable `smallFontSize` is now renamed to `fontSizeSm`
80+
- theme variable `mediumFontSize` is now renamed to `fontSizeMd`
81+
- theme variable `largeFontMedium` is now renamed to `fontSizeLg`
82+
- theme variable `requiredInvalidColor` is now removed
83+
- theme variable `borderStyle` is now removed
84+
- theme variable `borderTopColor` is now removed
85+
- theme variable `borderBottomColor` is now removed
86+
- theme variable `borderLeftColor` is now removed
87+
- theme variable `borderRightColor` is now removed
88+
- theme variable `color` is now renamed to `textColor`
89+
- theme variable `background` is now renamed to `backgroundColor`
90+
- theme variable `focusOutlineWidth` is now removed
91+
- theme variable `focusOutlineStyle` is now removed
92+
- theme variable `focusOutlineColor` is now removed
93+
94+
### NumberInput
95+
96+
- theme variable `requiredInvalidColor` is now removed
97+
98+
### RadioInputGroup
99+
100+
- theme variable `invalidAsteriskColor` is now removed
101+
102+
### TextInput
103+
104+
- theme variable `requiredInvalidColor` is now removed
105+
54106
## Codemods
55107

56108
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:

packages/__docs__/buildScripts/ai-accessible-documentation/summaries-for-llms-file.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@
434434
},
435435
{
436436
"title": "form-errors",
437-
"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."
437+
"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."
438438
},
439439
{
440440
"title": "layout-spacing",

packages/ui-checkbox/src/Checkbox/index.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,10 @@ class Checkbox extends Component<CheckboxProps, CheckboxState> {
185185
return isActiveElement(this._input)
186186
}
187187

188-
get isNewError() {
189-
return !!this.props.messages?.find((m) => m.type === 'newError')
188+
get isError() {
189+
return !!this.props.messages?.find(
190+
(m) => m.type === 'error' || m.type === 'newError'
191+
)
190192
}
191193

192194
get invalid() {
@@ -278,7 +280,7 @@ class Checkbox extends Component<CheckboxProps, CheckboxState> {
278280
display="block"
279281
margin="small 0 0"
280282
css={
281-
this.isNewError &&
283+
this.isError &&
282284
(variant === 'toggle'
283285
? styles?.indentedToggleError
284286
: styles?.indentedError)

packages/ui-date-time-input/src/DateTimeInput/index.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
162162
? this.props.invalidDateTimeMessage(parsed.toISOString(true))
163163
: this.props.invalidDateTimeMessage
164164
}
165-
errorMsg = text ? { text, type: 'newError' } : undefined
165+
errorMsg = text ? { text, type: 'error' } : undefined
166166
return {
167167
iso: parsed.clone(),
168168
calendarSelectedDate: parsed.clone(),
@@ -345,7 +345,7 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
345345
? this.props.invalidDateTimeMessage(dateStr ? dateStr : '')
346346
: this.props.invalidDateTimeMessage
347347
// eslint-disable-next-line no-param-reassign
348-
newState.message = { text: text, type: 'newError' }
348+
newState.message = { text: text, type: 'error' }
349349
}
350350
if (this.areDifferentDates(this.state.iso, newState.iso)) {
351351
if (typeof this.props.onChange === 'function') {
@@ -569,10 +569,12 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
569569
...(messages || [])
570570
]
571571

572-
const hasError = allMessages.find((m) => m.type === 'newError')
572+
const hasError = allMessages.find(
573+
(m) => m.type === 'newError' || m.type === 'error'
574+
)
573575
// 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
574576
const subComponentMessages: FormMessage[] = hasError
575-
? [{ type: 'newError', text: '' }]
577+
? [{ type: 'error', text: '' }]
576578
: []
577579

578580
return (

packages/ui-form-field/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"@instructure/ui-a11y-utils": "workspace:*",
4040
"@instructure/ui-grid": "workspace:*",
4141
"@instructure/ui-icons": "workspace:*",
42+
"@instructure/ui-icons-lucide": "workspace:*",
4243
"@instructure/ui-react-utils": "workspace:*",
4344
"@instructure/ui-utils": "workspace:*",
4445
"@instructure/uid": "workspace:*"

packages/ui-form-field/src/FormField/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,25 @@ type: example
1111
---
1212
<div>
1313
<FormField id="_foo121" label="Stacked layout" width="400px" layout="stacked"
14-
messages={[{type:'success', text: 'This is a success message'}, {type:'newError', text: 'An error message. It will wrap if the text is longer than the width of the container.'}]}>
14+
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.'}]}>
1515
<TextInput id="_foo121"/>
1616
</FormField>
1717
test
1818
<hr/>
1919
<FormField id="_foo122" label="Stacked layout (inline=true)" width="400px" layout="stacked" inline
20-
messages={[{type:'success', text: 'This is a success message'}, {type:'newError', text: 'An error message. It will wrap if the text is longer than the width of the container.'}]}>
20+
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.'}]}>
2121
<TextInput id="_foo122"/>
2222
</FormField>
2323
test
2424
<hr/>
2525
<FormField id="_foo123" label="Inline layout" width="400px" layout="inline"
26-
messages={[{type:'success', text: 'success!'}, {type:'newError', text: 'An error message. It will wrap if the text is longer than the width of the container.'}]}>
26+
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.'}]}>
2727
<TextInput id="_foo123"/>
2828
</FormField>
2929
test
3030
<hr/>
3131
<FormField id="_foo124" label="Inline layout (inline=true)" width="400px" layout="inline" inline
32-
messages={[{type:'success', text: 'success!'}, {type:'newError', text: 'An error message. It will wrap if the text is longer than the width of the container.'}]}>
32+
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.'}]}>
3333
<TextInput id="_foo124"/>
3434
</FormField>
3535
test

packages/ui-form-field/src/FormField/index.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ import { Component } from 'react'
2626

2727
import { omitProps, pickProps } from '@instructure/ui-react-utils'
2828

29-
import { FormFieldLayout } from '../FormFieldLayout'
29+
import {
30+
FormFieldLayout,
31+
allowedProps as formFieldLayoutAllowedProps
32+
} from '../FormFieldLayout'
3033

3134
import { allowedProps } from './props'
3235
import type { FormFieldProps } from './props'
@@ -63,7 +66,7 @@ class FormField extends Component<FormFieldProps> {
6366
return (
6467
<FormFieldLayout
6568
{...omitProps(this.props, FormField.allowedProps)}
66-
{...pickProps(this.props, FormFieldLayout.allowedProps)}
69+
{...pickProps(this.props, formFieldLayoutAllowedProps)}
6770
label={this.props.label}
6871
vAlign={this.props.vAlign}
6972
as="label"
@@ -73,6 +76,7 @@ class FormField extends Component<FormFieldProps> {
7376
htmlFor={this.props.id}
7477
elementRef={this.handleRef}
7578
margin={this.props.margin}
79+
isRequired={this.props.isRequired}
7680
/>
7781
)
7882
}

packages/ui-form-field/src/FormField/props.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ type FormFieldOwnProps = {
5757
* provides a reference to the underlying html root element
5858
*/
5959
elementRef?: (element: Element | null) => void
60+
/**
61+
* If `true`, displays an asterisk after the label to indicate the field is required
62+
*/
63+
isRequired?: boolean
6064

6165
/**
6266
* Margin around the component. Accepts a `Spacing` token. See token values and example usage in [this guide](https://instructure.design/#layout-spacing).
@@ -82,6 +86,7 @@ const allowedProps: AllowedPropKeys = [
8286
'width',
8387
'inputContainerRef',
8488
'elementRef',
89+
'isRequired',
8590
'margin'
8691
]
8792

packages/ui-form-field/src/FormFieldGroup/index.tsx

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,16 @@ import { Grid } from '@instructure/ui-grid'
2828
import { pickProps, omitProps } from '@instructure/ui-react-utils'
2929
import { withStyleRework as withStyle } from '@instructure/emotion'
3030

31-
import { FormFieldLayout } from '../FormFieldLayout'
31+
import {
32+
FormFieldLayout,
33+
allowedProps as formFieldLayoutAllowedProps
34+
} from '../FormFieldLayout'
3235

3336
import generateStyle from './styles'
3437
import generateComponentTheme from './theme'
3538

3639
import { allowedProps } from './props'
37-
import type { FormFieldGroupProps, FormFieldGroupStyleProps } from './props'
40+
import type { FormFieldGroupProps } from './props'
3841

3942
/**
4043
---
@@ -68,21 +71,11 @@ class FormFieldGroup extends Component<FormFieldGroupProps> {
6871
}
6972

7073
componentDidMount() {
71-
this.props.makeStyles?.(this.makeStylesVariables)
74+
this.props.makeStyles?.()
7275
}
7376

7477
componentDidUpdate() {
75-
this.props.makeStyles?.(this.makeStylesVariables)
76-
}
77-
78-
get makeStylesVariables(): FormFieldGroupStyleProps {
79-
// new form errors dont need borders
80-
const oldInvalid =
81-
!!this.props.messages &&
82-
this.props.messages.findIndex((message) => {
83-
return message.type === 'error'
84-
}) >= 0
85-
return { invalid: oldInvalid }
78+
this.props.makeStyles?.()
8679
}
8780

8881
get invalid() {
@@ -172,7 +165,7 @@ class FormFieldGroup extends Component<FormFieldGroupProps> {
172165
return (
173166
<FormFieldLayout
174167
{...omitProps(props, FormFieldGroup.allowedProps)}
175-
{...pickProps(props, FormFieldLayout.allowedProps)}
168+
{...pickProps(props, formFieldLayoutAllowedProps)}
176169
vAlign={props.vAlign}
177170
layout={props.layout === 'inline' ? 'inline' : 'stacked'}
178171
label={props.description}

0 commit comments

Comments
 (0)