Skip to content

Commit 6a10c93

Browse files
committed
feat(many): rework TextArea and dependent FormField components
INSTUI-4812
1 parent 620b421 commit 6a10c93

File tree

39 files changed

+798
-842
lines changed

39 files changed

+798
-842
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: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ InstUI v12 introduces a new icon package **`@instructure/ui-icons-lucide`** base
2626
| **color** | Limited tokens: `'primary'` \| `'secondary'` \| `'success'` \| `'error'` \| `'warning'` \| etc. | 60+ theme tokens (`'baseColor'`, `'successColor'`, `'actionPrimaryBaseColor'`, etc.) or any CSS color |
2727
| **strokeWidth** | ❌ Not available | `'xs'` \| `'sm'` \| `'md'` \| `'lg'` \| `'xl'` \| `'2xl'` \| `number` \| `string` |
2828
| **children** | `React.ReactNode` | ❌ Removed |
29+
| |
2930
| **focusable** | `boolean` | ❌ Removed |
3031
| **description** | `string` (combined with title) | ❌ Removed (use `title` only) |
3132
| **src** | `string` | ❌ Removed |
@@ -41,6 +42,58 @@ The new icons automatically sync with theme changes, support all InstUI color to
4142
- Setting `readonly` does not set the low level `<input>` to disabled, but to `readonly`. This also means that the input is still focusable when `readonly`
4243
- its DOM structure has been significantly simplified
4344

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

4699
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-checkbox/src/CheckboxGroup/__tests__/CheckboxGroup.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ describe('<CheckboxGroup />', () => {
222222
it('adds the correct ARIA attributes', () => {
223223
const { container } = renderCheckboxGroup({
224224
disabled: true,
225-
messages: [{ type: 'newError', text: 'abc' }],
225+
messages: [{ type: 'error', text: 'abc' }],
226226
// @ts-ignore This is a valid attribute
227227
'data-id': 'group'
228228
})

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

0 commit comments

Comments
 (0)