Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions docs/guides/form-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ type: code
---
type FormMessages = {
type:
| 'newError'
| 'error'
| 'hint'
| 'success'
Expand All @@ -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 (
<TextInput
Expand All @@ -48,11 +47,9 @@ const PasswordExample = () => {
render(<PasswordExample/>)
```

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:

Expand All @@ -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'))
}
Expand All @@ -83,10 +78,9 @@ const Example = () => {
name="errorOptions"
description="Error message options"
onChange={handleSettingsChange}
defaultValue={['showError', 'showNewError', 'isRequired']}
defaultValue={['showError', 'isRequired']}
>
<Checkbox label="Show error message" value="showError"/>
<Checkbox label="Use the new error type" value="showNewError" />
<Checkbox label="Use long message" value="showLongError" />
<Checkbox label="Make fields required" value="isRequired" />
</CheckboxGroup>
Expand Down
56 changes: 56 additions & 0 deletions docs/guides/upgrade-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 5 additions & 3 deletions packages/ui-checkbox/src/Checkbox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,10 @@ class Checkbox extends Component<CheckboxProps, CheckboxState> {
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() {
Expand Down Expand Up @@ -278,7 +280,7 @@ class Checkbox extends Component<CheckboxProps, CheckboxState> {
display="block"
margin="small 0 0"
css={
this.isNewError &&
this.isError &&
(variant === 'toggle'
? styles?.indentedToggleError
: styles?.indentedError)
Expand Down
10 changes: 6 additions & 4 deletions packages/ui-date-time-input/src/DateTimeInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
? 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(),
Expand Down Expand Up @@ -345,7 +345,7 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
? 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') {
Expand Down Expand Up @@ -569,10 +569,12 @@ class DateTimeInput extends Component<DateTimeInputProps, DateTimeInputState> {
...(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 (
Expand Down
1 change: 1 addition & 0 deletions packages/ui-form-field/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:*"
Expand Down
8 changes: 4 additions & 4 deletions packages/ui-form-field/src/FormField/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,25 @@ type: example
---
<div>
<FormField id="_foo121" label="Stacked layout" width="400px" layout="stacked"
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.'}]}>
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.'}]}>
<TextInput id="_foo121"/>
</FormField>
test
<hr/>
<FormField id="_foo122" label="Stacked layout (inline=true)" width="400px" layout="stacked" inline
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.'}]}>
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.'}]}>
<TextInput id="_foo122"/>
</FormField>
test
<hr/>
<FormField id="_foo123" label="Inline layout" width="400px" layout="inline"
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.'}]}>
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.'}]}>
<TextInput id="_foo123"/>
</FormField>
test
<hr/>
<FormField id="_foo124" label="Inline layout (inline=true)" width="400px" layout="inline" inline
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.'}]}>
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.'}]}>
<TextInput id="_foo124"/>
</FormField>
test
Expand Down
10 changes: 8 additions & 2 deletions packages/ui-form-field/src/FormField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -63,7 +66,7 @@ class FormField extends Component<FormFieldProps> {
return (
<FormFieldLayout
{...omitProps(this.props, FormField.allowedProps)}
{...pickProps(this.props, FormFieldLayout.allowedProps)}
{...pickProps(this.props, formFieldLayoutAllowedProps)}
label={this.props.label}
vAlign={this.props.vAlign}
as="label"
Expand All @@ -73,6 +76,9 @@ class FormField extends Component<FormFieldProps> {
htmlFor={this.props.id}
elementRef={this.handleRef}
margin={this.props.margin}
isRequired={this.props.isRequired}
disabled={this.props.disabled}
readOnly={this.props.readOnly}
/>
)
}
Expand Down
17 changes: 16 additions & 1 deletion packages/ui-form-field/src/FormField/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -82,7 +94,10 @@ const allowedProps: AllowedPropKeys = [
'width',
'inputContainerRef',
'elementRef',
'margin'
'isRequired',
'margin',
'disabled',
'readOnly'
]

export type { FormFieldOwnProps, FormFieldProps }
Expand Down
23 changes: 8 additions & 15 deletions packages/ui-form-field/src/FormFieldGroup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

/**
---
Expand Down Expand Up @@ -68,21 +71,11 @@ class FormFieldGroup extends Component<FormFieldGroupProps> {
}

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() {
Expand Down Expand Up @@ -172,7 +165,7 @@ class FormFieldGroup extends Component<FormFieldGroupProps> {
return (
<FormFieldLayout
{...omitProps(props, FormFieldGroup.allowedProps)}
{...pickProps(props, FormFieldLayout.allowedProps)}
{...pickProps(props, formFieldLayoutAllowedProps)}
vAlign={props.vAlign}
layout={props.layout === 'inline' ? 'inline' : 'stacked'}
label={props.description}
Expand Down
18 changes: 9 additions & 9 deletions packages/ui-form-field/src/FormFieldGroup/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,14 @@ type FormFieldGroupOwnProps = {
* id for the form field messages
*/
messagesId?: string
/**
* Whether the field group is disabled. When true, error and success messages will be hidden.
*/
disabled?: boolean
/**
* Whether the field group is read-only. When true, error and success messages will be hidden.
*/
readOnly?: boolean
children?: React.ReactNode
layout?: 'stacked' | 'columns' | 'inline'
rowSpacing?: 'none' | 'small' | 'medium' | 'large'
Expand All @@ -61,10 +68,6 @@ type FormFieldGroupOwnProps = {
elementRef?: (element: Element | null) => void
}

type FormFieldGroupStyleProps = {
invalid: boolean
}

type PropKeys = keyof FormFieldGroupOwnProps

type AllowedPropKeys = Readonly<Array<PropKeys>>
Expand All @@ -86,6 +89,7 @@ const allowedProps: AllowedPropKeys = [
'messages',
'messagesId',
'disabled',
'readOnly',
'children',
'layout',
'rowSpacing',
Expand All @@ -95,9 +99,5 @@ const allowedProps: AllowedPropKeys = [
'elementRef'
]

export type {
FormFieldGroupProps,
FormFieldGroupStyleProps,
FormFieldGroupStyle
}
export type { FormFieldGroupProps, FormFieldGroupStyle }
export { allowedProps }
Loading
Loading