Skip to content

Commit 9003d19

Browse files
committed
feat(many): backport new error messages from v10
1 parent dabbe23 commit 9003d19

File tree

53 files changed

+654
-101
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+654
-101
lines changed

.github/workflows/deploy.yml

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@ on:
77
- v9_maintenance
88
workflow_dispatch:
99
jobs:
10-
deploy-docs:
10+
deploy-release:
11+
if: startsWith(github.event.head_commit.message, 'chore(release)')
1112
runs-on: ubuntu-latest
1213
steps:
13-
- uses: actions/checkout@v3
14+
- uses: actions/checkout@v4
15+
- name: Install Node 22
16+
uses: actions/setup-node@v4
17+
with:
18+
node-version: '22'
1419
- run: npm ci && npm run bootstrap
1520
- name: Set build directory and deployment path based on branch
1621
id: set-build-dir
@@ -35,5 +40,29 @@ jobs:
3540
folder: ${{ steps.set-build-dir.outputs.build_dir }}
3641
branch: gh-pages
3742
target-folder: ${{ steps.set-build-dir.outputs.deploy_dir }}
38-
clean-exclude: pr-preview
43+
clean-exclude: |
44+
pr-preview
45+
v7
46+
v8
47+
v9
48+
latest
49+
force: false
50+
deploy-latest:
51+
runs-on: ubuntu-latest
52+
if: github.ref == 'refs/heads/master'
53+
steps:
54+
- uses: actions/checkout@v4
55+
- name: Install Node 22
56+
uses: actions/setup-node@v4
57+
with:
58+
node-version: '22'
59+
- run: npm ci && npm run bootstrap
60+
- name: Build docs-app
61+
run: npm run build:docs
62+
- name: Deploy to GitHub Pages
63+
uses: JamesIves/github-pages-deploy-action@v4
64+
with:
65+
folder: ./packages/__docs__/__build__
66+
branch: gh-pages
67+
target-folder: latest
3968
force: false

docs/guides/form-errors.md

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
---
2+
title: Form Errors
3+
category: Guides
4+
order: 7
5+
---
6+
7+
# Adding Error Messages to Form Components
8+
9+
InstUI offers a range of form elements and all of them have a similar API to handle error/hint/success messages. These components use the `messages` prop with the following type definition:
10+
11+
```ts
12+
---
13+
type: code
14+
---
15+
type FormMessages = {
16+
type:
17+
| 'newError'
18+
| 'error'
19+
| 'hint'
20+
| 'success'
21+
| 'screenreader-only'
22+
text: React.ReactNode
23+
}[]
24+
```
25+
26+
So a basic example would look something like this:
27+
28+
```ts
29+
---
30+
type: example
31+
---
32+
const PasswordExample = () => {
33+
const [password, setPassword] = useState('')
34+
const messages = password.length < 6
35+
? [{type: 'newError', text: 'Password have to be at least 6 characters long!'}]
36+
: []
37+
return (
38+
<TextInput
39+
renderLabel="Password"
40+
type="password"
41+
messages={messages}
42+
onChange={(event, value) => { setPassword(value) }}
43+
/>
44+
)
45+
}
46+
47+
render(<PasswordExample/>)
48+
```
49+
50+
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.
51+
52+
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.
53+
54+
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.
55+
56+
Here are examples with different form components:
57+
58+
```ts
59+
---
60+
type: example
61+
---
62+
const Example = () => {
63+
const [showError, setShowError] = useState(true)
64+
const [showNewError, setShowNewError] = useState(true)
65+
const [showLongError, setShowLongError] = useState(false)
66+
const [isRequired, setIsRequired] = useState(true)
67+
68+
const messages = showError
69+
? [{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'}]
70+
: []
71+
72+
const handleSettingsChange = (v) => {
73+
setShowError(v.includes('showError'))
74+
setShowNewError(v.includes('showNewError'))
75+
setShowLongError(v.includes('showLongError'))
76+
setIsRequired(v.includes('isRequired'))
77+
}
78+
79+
return (
80+
<div>
81+
<CheckboxGroup
82+
name="errorOptions"
83+
description="Error message options"
84+
onChange={handleSettingsChange}
85+
defaultValue={['showError', 'showNewError', 'isRequired']}
86+
>
87+
<Checkbox label="Show error message" value="showError"/>
88+
<Checkbox label="Use the new error type" value="showNewError" />
89+
<Checkbox label="Use long message" value="showLongError" />
90+
<Checkbox label="Make fields required" value="isRequired" />
91+
</CheckboxGroup>
92+
<div style={{display: 'flex', gap: '2rem', marginTop: '3rem', flexDirection: 'column'}}>
93+
94+
<TextInput renderLabel="TextInput" messages={messages} isRequired={isRequired}/>
95+
96+
<NumberInput renderLabel="NumberInput" messages={messages} isRequired={isRequired}/>
97+
98+
<TextArea messages={messages} label="TextArea" required={isRequired}/>
99+
100+
<Checkbox label="Checkbox" isRequired={isRequired} messages={messages}/>
101+
102+
<Checkbox label={`Checkbox (variant="toggle")`} variant="toggle" isRequired={isRequired} messages={messages}/>
103+
104+
<CheckboxGroup
105+
name="CheckboxGroup"
106+
messages={messages}
107+
description="CheckboxGroup"
108+
>
109+
<Checkbox label="Checkbox 1" value="checkbox1"/>
110+
<Checkbox label="Checkbox 2" value="checkbox2"/>
111+
<Checkbox label="Checkbox 3" value="checkbox3"/>
112+
</CheckboxGroup>
113+
114+
<RadioInputGroup name="radioInputGroup" description="RadioInputGroup" messages={messages} isRequired={isRequired}>
115+
<RadioInput
116+
label="RadioInput 1"
117+
value="radioInput1"
118+
/>
119+
<RadioInput
120+
label="RadioInput 2"
121+
value="radioInput2"
122+
/>
123+
<RadioInput
124+
label="RadioInput 3"
125+
value="radioInput3"
126+
/>
127+
</RadioInputGroup>
128+
129+
<FileDrop messages={messages} renderLabel="FileDrop" />
130+
131+
<ColorPicker
132+
label="ColorPicker"
133+
placeholderText="Enter HEX"
134+
isRequired={isRequired}
135+
renderMessages={() => messages}
136+
/>
137+
138+
</div>
139+
</div>
140+
)
141+
}
142+
143+
render(<Example/>)
144+
```

package-lock.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/shared-types/src/ComponentThemeVariables.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ export type CheckboxFacadeTheme = {
316316
iconSizeSmall: string
317317
iconSizeMedium: string
318318
iconSizeLarge: string
319+
errorBorderColor: Colors['borderDanger']
319320
}
320321

321322
export type ToggleFacadeTheme = {
@@ -343,6 +344,7 @@ export type ToggleFacadeTheme = {
343344
labelFontSizeSmall: Typography['fontSizeSmall']
344345
labelFontSizeMedium: Typography['fontSizeMedium']
345346
labelFontSizeLarge: Typography['fontSizeLarge']
347+
errorBorderColor: Colors['borderDanger']
346348
}
347349

348350
export type CodeEditorTheme = {
@@ -584,6 +586,7 @@ export type FormFieldMessageTheme = {
584586
fontWeight: Typography['fontWeightNormal']
585587
fontSize: Typography['fontSizeSmall']
586588
lineHeight: Typography['lineHeight']
589+
errorIconMarginRight: Spacing['xxSmall']
587590
}
588591

589592
export type FormFieldMessagesTheme = {
@@ -879,6 +882,7 @@ export type NumberInputTheme = {
879882
mediumHeight: Forms['inputHeightMedium']
880883
largeFontSize: Typography['fontSizeLarge']
881884
largeHeight: Forms['inputHeightLarge']
885+
requiredInvalidColor: Colors['textDanger']
882886
}
883887

884888
export type OptionsItemTheme = {
@@ -1086,6 +1090,10 @@ export type RadioInputTheme = {
10861090
toggleLargeFontSize: Typography['fontSizeMedium']
10871091
}
10881092

1093+
export type RadioInputGroupTheme = {
1094+
invalidAsteriskColor: Colors['textDanger']
1095+
}
1096+
10891097
export type RangeInputTheme = {
10901098
minWidth: string | 0
10911099
handleSize: string | 0
@@ -1364,6 +1372,7 @@ export type TextAreaTheme = {
13641372
mediumHeight: Forms['inputHeightMedium']
13651373
largeFontSize: Typography['fontSizeLarge']
13661374
largeHeight: Forms['inputHeightLarge']
1375+
requiredInvalidColor: Colors['textDanger']
13671376
}
13681377

13691378
export type TextInputTheme = {
@@ -1388,6 +1397,7 @@ export type TextInputTheme = {
13881397
mediumHeight: Forms['inputHeightMedium']
13891398
largeFontSize: Typography['fontSizeLarge']
13901399
largeHeight: Forms['inputHeightLarge']
1400+
requiredInvalidColor: Colors['textDanger']
13911401
}
13921402

13931403
export type ToggleDetailsTheme = {
@@ -1744,6 +1754,7 @@ export interface ThemeVariables {
17441754
ProgressCircle: ProgressCircleTheme
17451755
RangeInput: RangeInputTheme
17461756
RadioInput: RadioInputTheme
1757+
RadioInputGroup: RadioInputGroupTheme
17471758
RatingIcon: RatingIconTheme
17481759
'Rating.Icon': RatingIconTheme
17491760
Select: SelectTheme

packages/ui-checkbox/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@ const MyCheckbox = () => {
3737
[npm]: https://img.shields.io/npm/v/@instructure/ui-checkbox.svg
3838
[npm-url]: https://npmjs.com/package/@instructure/ui-checkbox
3939
[license-badge]: https://img.shields.io/npm/l/instructure-ui.svg?style=flat-square
40-
[license]: https://github.com/instructure/instructure-ui/blob/master/LICENSE
40+
[license]: https://github.com/instructure/instructure-ui/blob/master/LICENSE.md
4141
[coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square
4242
[coc]: https://github.com/instructure/instructure-ui/blob/master/CODE_OF_CONDUCT.md

packages/ui-checkbox/src/Checkbox/CheckboxFacade/props.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ type CheckboxFacadeOwnProps = {
4040
* Visual state showing that child checkboxes are a combination of checked and unchecked
4141
*/
4242
indeterminate?: boolean
43+
/**
44+
* Indicate if the parent component (`Checkbox`) is invalid to set the style accordingly.
45+
*/
46+
invalid?: boolean
4347
}
4448

4549
type PropKeys = keyof CheckboxFacadeOwnProps
@@ -57,7 +61,8 @@ const propTypes: PropValidators<PropKeys> = {
5761
focused: PropTypes.bool,
5862
hovered: PropTypes.bool,
5963
size: PropTypes.oneOf(['small', 'medium', 'large']),
60-
indeterminate: PropTypes.bool
64+
indeterminate: PropTypes.bool,
65+
invalid: PropTypes.bool
6166
}
6267

6368
const allowedProps: AllowedPropKeys = [

packages/ui-checkbox/src/Checkbox/CheckboxFacade/styles.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const generateStyle = (
3939
componentTheme: CheckboxFacadeTheme,
4040
props: CheckboxFacadeProps
4141
): CheckboxFacadeStyle => {
42-
const { size, checked, focused, hovered, indeterminate } = props
42+
const { size, checked, focused, hovered, indeterminate, invalid } = props
4343

4444
const isChecked = checked || indeterminate
4545

@@ -87,7 +87,9 @@ const generateStyle = (
8787
boxSizing: 'border-box',
8888
flexShrink: 0,
8989
transition: 'all 0.2s',
90-
border: `${componentTheme.borderWidth} solid ${componentTheme.borderColor}`,
90+
border: `${componentTheme.borderWidth} solid ${
91+
invalid ? componentTheme.errorBorderColor : componentTheme.borderColor
92+
}`,
9193
borderRadius: componentTheme.borderRadius,
9294
marginInlineEnd: componentTheme.marginRight,
9395
marginInlineStart: '0',

packages/ui-checkbox/src/Checkbox/CheckboxFacade/theme.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const generateComponentTheme = (theme: Theme): CheckboxFacadeTheme => {
5757
checkedBorderColor: colors?.borderDarkest,
5858

5959
hoverBorderColor: colors?.borderDarkest,
60+
errorBorderColor: colors?.borderDanger,
6061

6162
focusBorderColor: colors?.borderBrand,
6263
focusBorderWidth: borders?.widthMedium,

packages/ui-checkbox/src/Checkbox/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,14 @@ type: embed
273273
</Figure>
274274
</Guidelines>
275275
```
276+
277+
```js
278+
---
279+
type: embed
280+
---
281+
<Guidelines>
282+
<Figure recommendation="a11y" title="Accessibility">
283+
<Figure.Item>Do not add business logic to `onMouseOver` or `onMouseOut` events. These events are not triggered by keyboard navigation</Figure.Item>
284+
</Figure>
285+
</Guidelines>
286+
```

packages/ui-checkbox/src/Checkbox/ToggleFacade/props.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ type ToggleFacadeOwnProps = {
3939
focused?: boolean
4040
size?: 'small' | 'medium' | 'large'
4141
labelPlacement?: 'top' | 'start' | 'end'
42+
/**
43+
* Indicate if the parent component (`Checkbox`) is invalid to set the style accordingly.
44+
*/
45+
invalid?: boolean
4246
}
4347

4448
type PropKeys = keyof ToggleFacadeOwnProps
@@ -59,7 +63,8 @@ const propTypes: PropValidators<PropKeys> = {
5963
readOnly: PropTypes.bool,
6064
focused: PropTypes.bool,
6165
size: PropTypes.oneOf(['small', 'medium', 'large']),
62-
labelPlacement: PropTypes.oneOf(['top', 'start', 'end'])
66+
labelPlacement: PropTypes.oneOf(['top', 'start', 'end']),
67+
invalid: PropTypes.bool
6368
}
6469

6570
const allowedProps: AllowedPropKeys = [

0 commit comments

Comments
 (0)