@instructure/ui-form-field 9.11.0 → 9.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/es/FormField/index.js +5 -1
- package/es/FormFieldGroup/__new-tests__/FormFieldGroup.test.js +4 -4
- package/es/FormFieldGroup/index.js +18 -4
- package/es/FormFieldLabel/index.js +6 -6
- package/es/FormFieldLayout/__new-tests__/FormFieldLayout.test.js +10 -8
- package/es/FormFieldLayout/index.js +79 -58
- package/es/FormFieldLayout/props.js +1 -6
- package/es/FormFieldLayout/styles.js +105 -9
- package/es/FormFieldLayout/theme.js +56 -0
- package/es/FormFieldMessages/props.js +3 -2
- package/es/FormFieldMessages/styles.js +5 -3
- package/es/FormPropTypes.js +6 -0
- package/lib/FormField/index.js +5 -1
- package/lib/FormFieldGroup/__new-tests__/FormFieldGroup.test.js +4 -4
- package/lib/FormFieldGroup/index.js +18 -4
- package/lib/FormFieldLabel/index.js +6 -5
- package/lib/FormFieldLayout/__new-tests__/FormFieldLayout.test.js +9 -7
- package/lib/FormFieldLayout/index.js +77 -58
- package/lib/FormFieldLayout/props.js +1 -6
- package/lib/FormFieldLayout/styles.js +105 -9
- package/lib/FormFieldLayout/theme.js +62 -0
- package/lib/FormFieldMessages/props.js +3 -2
- package/lib/FormFieldMessages/styles.js +5 -3
- package/lib/FormPropTypes.js +6 -0
- package/package.json +15 -15
- package/src/FormField/README.md +31 -3
- package/src/FormField/index.tsx +3 -0
- package/src/FormFieldGroup/__new-tests__/FormFieldGroup.test.tsx +4 -6
- package/src/FormFieldGroup/index.tsx +41 -6
- package/src/FormFieldLabel/index.tsx +8 -3
- package/src/FormFieldLayout/__new-tests__/FormFieldLayout.test.tsx +6 -8
- package/src/FormFieldLayout/index.tsx +83 -100
- package/src/FormFieldLayout/props.ts +30 -7
- package/src/FormFieldLayout/styles.ts +124 -12
- package/src/FormFieldLayout/theme.ts +59 -0
- package/src/FormFieldMessages/props.ts +8 -2
- package/src/FormFieldMessages/styles.ts +5 -4
- package/src/FormPropTypes.ts +4 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/FormField/index.d.ts.map +1 -1
- package/types/FormFieldGroup/index.d.ts +1 -0
- package/types/FormFieldGroup/index.d.ts.map +1 -1
- package/types/FormFieldLabel/index.d.ts +2 -2
- package/types/FormFieldLabel/index.d.ts.map +1 -1
- package/types/FormFieldLayout/index.d.ts +8 -7
- package/types/FormFieldLayout/index.d.ts.map +1 -1
- package/types/FormFieldLayout/props.d.ts +27 -3
- package/types/FormFieldLayout/props.d.ts.map +1 -1
- package/types/FormFieldLayout/styles.d.ts +4 -3
- package/types/FormFieldLayout/styles.d.ts.map +1 -1
- package/types/FormFieldLayout/theme.d.ts +10 -0
- package/types/FormFieldLayout/theme.d.ts.map +1 -0
- package/types/FormFieldMessages/index.d.ts +8 -2
- package/types/FormFieldMessages/index.d.ts.map +1 -1
- package/types/FormFieldMessages/props.d.ts +5 -0
- package/types/FormFieldMessages/props.d.ts.map +1 -1
- package/types/FormFieldMessages/styles.d.ts +2 -3
- package/types/FormFieldMessages/styles.d.ts.map +1 -1
- package/types/FormPropTypes.d.ts +3 -0
- package/types/FormPropTypes.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [9.11.1](https://github.com/instructure/instructure-ui/compare/v9.11.0...v9.11.1) (2025-03-11)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* fix FormFieldLabel not read on NVDA hover ([c29b6ab](https://github.com/instructure/instructure-ui/commit/c29b6ab989266c6a7875e3f0dbdee3efbf18606a))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
# [9.11.0](https://github.com/instructure/instructure-ui/compare/v9.10.2...v9.11.0) (2025-02-12)
|
|
7
18
|
|
|
8
19
|
**Note:** Version bump only for package @instructure/ui-form-field
|
package/es/FormField/index.js
CHANGED
|
@@ -47,7 +47,11 @@ class FormField extends Component {
|
|
|
47
47
|
return /*#__PURE__*/React.createElement(FormFieldLayout, Object.assign({}, omitProps(this.props, FormField.allowedProps), pickProps(this.props, FormFieldLayout.allowedProps), {
|
|
48
48
|
label: this.props.label,
|
|
49
49
|
vAlign: this.props.vAlign,
|
|
50
|
-
as: "label"
|
|
50
|
+
as: "label"
|
|
51
|
+
// This makes the control in focus when the label is clicked
|
|
52
|
+
// This is needed to prevent the wrong element to be focused, e.g.
|
|
53
|
+
// multi selects Tag-s
|
|
54
|
+
,
|
|
51
55
|
htmlFor: this.props.id,
|
|
52
56
|
elementRef: this.handleRef
|
|
53
57
|
}));
|
|
@@ -46,7 +46,7 @@ describe('<FormFieldGroup />', () => {
|
|
|
46
46
|
description: "Please enter your full name"
|
|
47
47
|
}, /*#__PURE__*/React.createElement("label", null, "First: ", /*#__PURE__*/React.createElement("input", null)), /*#__PURE__*/React.createElement("label", null, "Middle: ", /*#__PURE__*/React.createElement("input", null)), /*#__PURE__*/React.createElement("label", null, "Last: ", /*#__PURE__*/React.createElement("input", null))))),
|
|
48
48
|
container = _render.container;
|
|
49
|
-
const formFieldGroup = container.querySelector("
|
|
49
|
+
const formFieldGroup = container.querySelector("span[class$='-formFieldLayout__label']");
|
|
50
50
|
const firstNameInput = screen.getByLabelText('First:');
|
|
51
51
|
const middleNameInput = screen.getByLabelText('Middle:');
|
|
52
52
|
const lastNameInput = screen.getByLabelText('Last:');
|
|
@@ -64,7 +64,7 @@ describe('<FormFieldGroup />', () => {
|
|
|
64
64
|
description: "Please enter your full name"
|
|
65
65
|
}, children)),
|
|
66
66
|
container = _render2.container;
|
|
67
|
-
const formFieldGroup = container.querySelector(
|
|
67
|
+
const formFieldGroup = container.querySelector('label');
|
|
68
68
|
expect(formFieldGroup).toBeInTheDocument();
|
|
69
69
|
});
|
|
70
70
|
it('links the messages to the fieldset via aria-describedby', () => {
|
|
@@ -86,13 +86,13 @@ describe('<FormFieldGroup />', () => {
|
|
|
86
86
|
expect(message).toHaveTextContent('Invalid name');
|
|
87
87
|
expect(message).toHaveAttribute('id', messagesId);
|
|
88
88
|
});
|
|
89
|
-
it('displays description message inside the
|
|
89
|
+
it('displays description message inside the label', () => {
|
|
90
90
|
const description = 'Please enter your full name';
|
|
91
91
|
const _render4 = render( /*#__PURE__*/React.createElement(FormFieldGroup, {
|
|
92
92
|
description: description
|
|
93
93
|
}, _label5 || (_label5 = /*#__PURE__*/React.createElement("label", null, "First: ", /*#__PURE__*/React.createElement("input", null))), _label6 || (_label6 = /*#__PURE__*/React.createElement("label", null, "Middle: ", /*#__PURE__*/React.createElement("input", null))), _label7 || (_label7 = /*#__PURE__*/React.createElement("label", null, "Last: ", /*#__PURE__*/React.createElement("input", null))))),
|
|
94
94
|
container = _render4.container;
|
|
95
|
-
const legend = container.querySelector("
|
|
95
|
+
const legend = container.querySelector("span[class$='-formFieldLayout__label']");
|
|
96
96
|
expect(legend).toBeInTheDocument();
|
|
97
97
|
expect(legend).toHaveTextContent(description);
|
|
98
98
|
});
|
|
@@ -60,13 +60,17 @@ let FormFieldGroup = (_dec = withStyle(generateStyle, generateComponentTheme), _
|
|
|
60
60
|
(_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2, this.makeStylesVariables);
|
|
61
61
|
}
|
|
62
62
|
get makeStylesVariables() {
|
|
63
|
+
// new form errors dont need borders
|
|
64
|
+
const oldInvalid = !!this.props.messages && this.props.messages.findIndex(message => {
|
|
65
|
+
return message.type === 'error';
|
|
66
|
+
}) >= 0;
|
|
63
67
|
return {
|
|
64
|
-
invalid:
|
|
68
|
+
invalid: oldInvalid
|
|
65
69
|
};
|
|
66
70
|
}
|
|
67
71
|
get invalid() {
|
|
68
72
|
return !!this.props.messages && this.props.messages.findIndex(message => {
|
|
69
|
-
return message.type === 'error';
|
|
73
|
+
return message.type === 'error' || message.type === 'newError';
|
|
70
74
|
}) >= 0;
|
|
71
75
|
}
|
|
72
76
|
renderColumns() {
|
|
@@ -98,12 +102,21 @@ let FormFieldGroup = (_dec = withStyle(generateStyle, generateComponentTheme), _
|
|
|
98
102
|
makeStyles = _this$props3.makeStyles,
|
|
99
103
|
isGroup = _this$props3.isGroup,
|
|
100
104
|
props = _objectWithoutProperties(_this$props3, _excluded);
|
|
105
|
+
// This is quite ugly, but according to ARIA spec the `aria-invalid` prop
|
|
106
|
+
// can only be used with certain roles see
|
|
107
|
+
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-invalid#associated_roles
|
|
108
|
+
// `aria-invalid` is put on in FormFieldLayout because the error message
|
|
109
|
+
// DOM part gets there its ID.
|
|
110
|
+
let ariaInvalid = void 0;
|
|
111
|
+
if (this.props.role && this.invalid && ['application', 'checkbox', 'combobox', 'gridcell', 'listbox', 'radiogroup', 'slider', 'spinbutton', 'textbox', 'tree', 'columnheader', 'rowheader', 'searchbox', 'switch', 'treegrid'].includes(this.props.role)) {
|
|
112
|
+
ariaInvalid = 'true';
|
|
113
|
+
}
|
|
101
114
|
return jsx(FormFieldLayout, Object.assign({}, omitProps(props, FormFieldGroup.allowedProps), pickProps(props, FormFieldLayout.allowedProps), {
|
|
102
115
|
vAlign: props.vAlign,
|
|
103
116
|
layout: props.layout === 'inline' ? 'inline' : 'stacked',
|
|
104
117
|
label: props.description,
|
|
105
118
|
"aria-disabled": props.disabled ? 'true' : void 0,
|
|
106
|
-
"aria-invalid":
|
|
119
|
+
"aria-invalid": ariaInvalid,
|
|
107
120
|
elementRef: this.handleRef,
|
|
108
121
|
isGroup: isGroup
|
|
109
122
|
}), this.renderFields());
|
|
@@ -113,7 +126,8 @@ let FormFieldGroup = (_dec = withStyle(generateStyle, generateComponentTheme), _
|
|
|
113
126
|
disabled: false,
|
|
114
127
|
rowSpacing: 'medium',
|
|
115
128
|
colSpacing: 'small',
|
|
116
|
-
vAlign: 'middle'
|
|
129
|
+
vAlign: 'middle',
|
|
130
|
+
isGroup: true
|
|
117
131
|
}, _FormFieldGroup)) || _class);
|
|
118
132
|
export default FormFieldGroup;
|
|
119
133
|
export { FormFieldGroup };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var _dec, _class, _FormFieldLabel;
|
|
1
|
+
var _dec, _dec2, _class, _FormFieldLabel;
|
|
2
2
|
/*
|
|
3
3
|
* The MIT License (MIT)
|
|
4
4
|
*
|
|
@@ -25,7 +25,7 @@ var _dec, _class, _FormFieldLabel;
|
|
|
25
25
|
|
|
26
26
|
/** @jsx jsx */
|
|
27
27
|
import { Component } from 'react';
|
|
28
|
-
import { omitProps, getElementType } from '@instructure/ui-react-utils';
|
|
28
|
+
import { omitProps, getElementType, deprecated } from '@instructure/ui-react-utils';
|
|
29
29
|
import { withStyle, jsx } from '@instructure/emotion';
|
|
30
30
|
import generateStyle from './styles';
|
|
31
31
|
import generateComponentTheme from './theme';
|
|
@@ -35,8 +35,7 @@ import { propTypes, allowedProps } from './props';
|
|
|
35
35
|
parent: FormField
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
-
This is a helper component that is used by most of the custom form
|
|
39
|
-
components. In most cases it shouldn't be used directly.
|
|
38
|
+
This is a helper component that is used by most of the custom form components. In most cases it shouldn't be used directly.
|
|
40
39
|
|
|
41
40
|
```js
|
|
42
41
|
---
|
|
@@ -45,8 +44,9 @@ type: example
|
|
|
45
44
|
<FormFieldLabel>Hello</FormFieldLabel>
|
|
46
45
|
```
|
|
47
46
|
|
|
47
|
+
@deprecated This is an internal component that will be removed in the future
|
|
48
48
|
**/
|
|
49
|
-
let FormFieldLabel = (_dec = withStyle(generateStyle, generateComponentTheme), _dec(_class = (_FormFieldLabel = class FormFieldLabel extends Component {
|
|
49
|
+
let FormFieldLabel = (_dec = withStyle(generateStyle, generateComponentTheme), _dec2 = deprecated('10', null, 'This component will be removed in a future version'), _dec(_class = _dec2(_class = (_FormFieldLabel = class FormFieldLabel extends Component {
|
|
50
50
|
constructor(...args) {
|
|
51
51
|
super(...args);
|
|
52
52
|
this.ref = null;
|
|
@@ -74,6 +74,6 @@ let FormFieldLabel = (_dec = withStyle(generateStyle, generateComponentTheme), _
|
|
|
74
74
|
}
|
|
75
75
|
}, _FormFieldLabel.displayName = "FormFieldLabel", _FormFieldLabel.componentId = 'FormFieldLabel', _FormFieldLabel.propTypes = propTypes, _FormFieldLabel.allowedProps = allowedProps, _FormFieldLabel.defaultProps = {
|
|
76
76
|
as: 'span'
|
|
77
|
-
}, _FormFieldLabel)) || _class);
|
|
77
|
+
}, _FormFieldLabel)) || _class) || _class);
|
|
78
78
|
export default FormFieldLabel;
|
|
79
79
|
export { FormFieldLabel };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var _FormFieldLayout, _FormFieldLayout2
|
|
1
|
+
var _FormFieldLayout, _FormFieldLayout2;
|
|
2
2
|
/*
|
|
3
3
|
* The MIT License (MIT)
|
|
4
4
|
*
|
|
@@ -24,7 +24,7 @@ var _FormFieldLayout, _FormFieldLayout2, _input;
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import React from 'react';
|
|
27
|
-
import { render
|
|
27
|
+
import { render } from '@testing-library/react';
|
|
28
28
|
import { vi } from 'vitest';
|
|
29
29
|
import { runAxeCheck } from '@instructure/ui-axe-check';
|
|
30
30
|
import '@testing-library/jest-dom';
|
|
@@ -47,7 +47,7 @@ describe('<FormFieldLayout />', () => {
|
|
|
47
47
|
}))),
|
|
48
48
|
container = _render.container;
|
|
49
49
|
const formFieldLayout = container.querySelector("label[class$='-formFieldLayout']");
|
|
50
|
-
const formFieldLabel = container.querySelector("span[class$='-
|
|
50
|
+
const formFieldLabel = container.querySelector("span[class$='-formFieldLayout__label']");
|
|
51
51
|
expect(formFieldLayout).toBeInTheDocument();
|
|
52
52
|
expect(formFieldLabel).toBeInTheDocument();
|
|
53
53
|
expect(formFieldLabel).toHaveTextContent('Username');
|
|
@@ -62,13 +62,15 @@ describe('<FormFieldLayout />', () => {
|
|
|
62
62
|
});
|
|
63
63
|
it('should provide a ref to the input container', () => {
|
|
64
64
|
const inputContainerRef = vi.fn();
|
|
65
|
+
const ref = /*#__PURE__*/React.createRef();
|
|
65
66
|
render( /*#__PURE__*/React.createElement(FormFieldLayout, {
|
|
66
67
|
label: "Username",
|
|
67
68
|
inputContainerRef: inputContainerRef
|
|
68
|
-
},
|
|
69
|
-
type: "text"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
expect(
|
|
69
|
+
}, /*#__PURE__*/React.createElement("input", {
|
|
70
|
+
type: "text",
|
|
71
|
+
ref: ref
|
|
72
|
+
})));
|
|
73
|
+
expect(ref.current).toBeInstanceOf(HTMLInputElement);
|
|
74
|
+
expect(inputContainerRef).toHaveBeenCalledWith(ref.current.parentElement);
|
|
73
75
|
});
|
|
74
76
|
});
|
|
@@ -28,24 +28,23 @@ var _dec, _dec2, _class, _FormFieldLayout;
|
|
|
28
28
|
/** @jsx jsx */
|
|
29
29
|
import { Component } from 'react';
|
|
30
30
|
import { hasVisibleChildren } from '@instructure/ui-a11y-utils';
|
|
31
|
-
import {
|
|
32
|
-
import { Grid } from '@instructure/ui-grid';
|
|
33
|
-
import { logError as error } from '@instructure/console';
|
|
34
|
-
import { omitProps, pickProps, getElementType, withDeterministicId } from '@instructure/ui-react-utils';
|
|
31
|
+
import { omitProps, getElementType, withDeterministicId } from '@instructure/ui-react-utils';
|
|
35
32
|
import { withStyle, jsx } from '@instructure/emotion';
|
|
36
|
-
import { FormFieldLabel } from '../FormFieldLabel';
|
|
37
33
|
import { FormFieldMessages } from '../FormFieldMessages';
|
|
38
34
|
import generateStyle from './styles';
|
|
39
35
|
import { propTypes, allowedProps } from './props';
|
|
36
|
+
import generateComponentTheme from './theme';
|
|
37
|
+
|
|
40
38
|
/**
|
|
41
39
|
---
|
|
42
40
|
parent: FormField
|
|
43
41
|
---
|
|
44
42
|
**/
|
|
45
|
-
let FormFieldLayout = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle,
|
|
43
|
+
let FormFieldLayout = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, generateComponentTheme), _dec(_class = _dec2(_class = (_FormFieldLayout = class FormFieldLayout extends Component {
|
|
46
44
|
constructor(props) {
|
|
47
45
|
super(props);
|
|
48
46
|
this._messagesId = void 0;
|
|
47
|
+
this._labelId = void 0;
|
|
49
48
|
this.ref = null;
|
|
50
49
|
this.handleRef = el => {
|
|
51
50
|
const elementRef = this.props.elementRef;
|
|
@@ -54,71 +53,99 @@ let FormFieldLayout = (_dec = withDeterministicId(), _dec2 = withStyle(generateS
|
|
|
54
53
|
elementRef(el);
|
|
55
54
|
}
|
|
56
55
|
};
|
|
56
|
+
this.makeStyleProps = () => {
|
|
57
|
+
var _this$props$messages;
|
|
58
|
+
const hasNewErrorMsgAndIsGroup = !!((_this$props$messages = this.props.messages) !== null && _this$props$messages !== void 0 && _this$props$messages.find(m => m.type === 'newError')) && !!this.props.isGroup;
|
|
59
|
+
return {
|
|
60
|
+
hasMessages: this.hasMessages,
|
|
61
|
+
hasVisibleLabel: this.hasVisibleLabel,
|
|
62
|
+
// if true render error message above the controls (and below the label)
|
|
63
|
+
hasNewErrorMsgAndIsGroup: hasNewErrorMsgAndIsGroup
|
|
64
|
+
};
|
|
65
|
+
};
|
|
57
66
|
this.handleInputContainerRef = node => {
|
|
58
67
|
if (typeof this.props.inputContainerRef === 'function') {
|
|
59
68
|
this.props.inputContainerRef(node);
|
|
60
69
|
}
|
|
61
70
|
};
|
|
62
71
|
this._messagesId = props.messagesId || props.deterministicId();
|
|
63
|
-
|
|
64
|
-
This will cause a layout issue in Internet Explorer 11 unless you also add a value for the 'width' prop.`);
|
|
72
|
+
this._labelId = props.deterministicId('FormField-Label');
|
|
65
73
|
}
|
|
66
74
|
componentDidMount() {
|
|
67
75
|
var _this$props$makeStyle, _this$props;
|
|
68
|
-
(_this$props$makeStyle = (_this$props = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props);
|
|
76
|
+
(_this$props$makeStyle = (_this$props = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props, this.makeStyleProps());
|
|
69
77
|
}
|
|
70
78
|
componentDidUpdate() {
|
|
71
79
|
var _this$props$makeStyle2, _this$props2;
|
|
72
|
-
(_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2);
|
|
80
|
+
(_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2, this.makeStyleProps());
|
|
73
81
|
}
|
|
74
82
|
get hasVisibleLabel() {
|
|
75
|
-
return this.props.label
|
|
83
|
+
return this.props.label ? hasVisibleChildren(this.props.label) : false;
|
|
76
84
|
}
|
|
77
85
|
get hasMessages() {
|
|
78
|
-
|
|
86
|
+
if (!this.props.messages || this.props.messages.length == 0) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
for (const msg of this.props.messages) {
|
|
90
|
+
if (msg.text) {
|
|
91
|
+
if (typeof msg.text === 'string') {
|
|
92
|
+
return msg.text.length > 0;
|
|
93
|
+
}
|
|
94
|
+
// this is more complicated (e.g. an array, a React component,...)
|
|
95
|
+
// but we don't try to optimize here for these cases
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
79
100
|
}
|
|
80
101
|
get elementType() {
|
|
81
102
|
return getElementType(FormFieldLayout, this.props);
|
|
82
103
|
}
|
|
83
|
-
get inlineContainerAndLabel() {
|
|
84
|
-
// Return if both the component container and label will display inline
|
|
85
|
-
return this.props.inline && this.props.layout === 'inline';
|
|
86
|
-
}
|
|
87
104
|
renderLabel() {
|
|
88
105
|
if (this.hasVisibleLabel) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
var _this$props$styles2;
|
|
107
|
+
if (this.elementType == 'fieldset') {
|
|
108
|
+
var _this$props$styles;
|
|
109
|
+
// `legend` has some special built in CSS, this can only be reset
|
|
110
|
+
// this way https://stackoverflow.com/a/65866981/319473
|
|
111
|
+
return jsx("legend", {
|
|
112
|
+
style: {
|
|
113
|
+
display: 'contents'
|
|
114
|
+
}
|
|
115
|
+
}, jsx("span", {
|
|
116
|
+
css: (_this$props$styles = this.props.styles) === null || _this$props$styles === void 0 ? void 0 : _this$props$styles.formFieldLabel
|
|
117
|
+
}, this.props.label));
|
|
118
|
+
}
|
|
119
|
+
return jsx("span", {
|
|
120
|
+
css: (_this$props$styles2 = this.props.styles) === null || _this$props$styles2 === void 0 ? void 0 : _this$props$styles2.formFieldLabel
|
|
121
|
+
}, this.props.label);
|
|
122
|
+
} else if (this.props.label) {
|
|
123
|
+
if (this.elementType == 'fieldset') {
|
|
124
|
+
return jsx("legend", {
|
|
125
|
+
id: this._labelId,
|
|
126
|
+
style: {
|
|
127
|
+
display: 'contents'
|
|
128
|
+
}
|
|
129
|
+
}, this.props.label);
|
|
130
|
+
}
|
|
131
|
+
// needs to be wrapped because it needs an `id`
|
|
132
|
+
return jsx("div", {
|
|
133
|
+
id: this._labelId,
|
|
134
|
+
style: {
|
|
135
|
+
display: 'contents'
|
|
136
|
+
}
|
|
137
|
+
}, this.props.label);
|
|
138
|
+
} else return null;
|
|
110
139
|
}
|
|
111
140
|
renderVisibleMessages() {
|
|
112
|
-
return this.hasMessages ? jsx(
|
|
113
|
-
offset: this.inlineContainerAndLabel ? void 0 : 3,
|
|
114
|
-
textAlign: this.inlineContainerAndLabel ? 'end' : void 0
|
|
115
|
-
}, jsx(FormFieldMessages, {
|
|
141
|
+
return this.hasMessages ? jsx(FormFieldMessages, {
|
|
116
142
|
id: this._messagesId,
|
|
117
|
-
messages: this.props.messages
|
|
118
|
-
|
|
143
|
+
messages: this.props.messages,
|
|
144
|
+
gridArea: "messages"
|
|
145
|
+
}) : null;
|
|
119
146
|
}
|
|
120
147
|
render() {
|
|
121
|
-
//
|
|
148
|
+
// Should be `<label>` if it's a FormField, fieldset if it's a group
|
|
122
149
|
const ElementType = this.elementType;
|
|
123
150
|
const _this$props3 = this.props,
|
|
124
151
|
makeStyles = _this$props3.makeStyles,
|
|
@@ -127,26 +154,20 @@ let FormFieldLayout = (_dec = withDeterministicId(), _dec2 = withStyle(generateS
|
|
|
127
154
|
isGroup = _this$props3.isGroup,
|
|
128
155
|
props = _objectWithoutProperties(_this$props3, _excluded);
|
|
129
156
|
const width = props.width,
|
|
130
|
-
layout = props.layout,
|
|
131
157
|
children = props.children;
|
|
132
|
-
const
|
|
133
|
-
return jsx(ElementType, Object.assign({}, omitProps(props, [...FormFieldLayout.allowedProps
|
|
158
|
+
const hasNewErrorMsgAndIsGroup = !!(messages !== null && messages !== void 0 && messages.find(m => m.type === 'newError')) && isGroup;
|
|
159
|
+
return jsx(ElementType, Object.assign({}, omitProps(props, [...FormFieldLayout.allowedProps]), {
|
|
134
160
|
css: styles === null || styles === void 0 ? void 0 : styles.formFieldLayout,
|
|
161
|
+
"aria-describedby": this.hasMessages ? this._messagesId : void 0,
|
|
162
|
+
"aria-errormessage": this.props['aria-invalid'] ? this._messagesId : void 0,
|
|
135
163
|
style: {
|
|
136
164
|
width
|
|
137
165
|
},
|
|
138
|
-
"aria-describedby": this.hasMessages ? this._messagesId : void 0,
|
|
139
166
|
ref: this.handleRef
|
|
140
|
-
}), this.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}, pickProps(props, Grid.allowedProps)), jsx(Grid.Row, null, this.renderLabel(), jsx(Grid.Col, {
|
|
145
|
-
width: this.inlineContainerAndLabel ? 'auto' : void 0,
|
|
146
|
-
elementRef: this.handleInputContainerRef
|
|
147
|
-
}, hasNewErrorMsg && jsx("div", {
|
|
148
|
-
css: styles === null || styles === void 0 ? void 0 : styles.groupErrorMessage
|
|
149
|
-
}, this.renderVisibleMessages()), children)), !hasNewErrorMsg && this.renderVisibleMessages()));
|
|
167
|
+
}), this.renderLabel(), hasNewErrorMsgAndIsGroup && this.renderVisibleMessages(), jsx("span", {
|
|
168
|
+
css: styles === null || styles === void 0 ? void 0 : styles.formFieldChildren,
|
|
169
|
+
ref: this.handleInputContainerRef
|
|
170
|
+
}, children), !hasNewErrorMsgAndIsGroup && this.renderVisibleMessages());
|
|
150
171
|
}
|
|
151
172
|
}, _FormFieldLayout.displayName = "FormFieldLayout", _FormFieldLayout.componentId = 'FormFieldLayout', _FormFieldLayout.propTypes = propTypes, _FormFieldLayout.allowedProps = allowedProps, _FormFieldLayout.defaultProps = {
|
|
152
173
|
inline: false,
|
|
@@ -40,10 +40,5 @@ const propTypes = {
|
|
|
40
40
|
elementRef: PropTypes.func,
|
|
41
41
|
isGroup: PropTypes.bool
|
|
42
42
|
};
|
|
43
|
-
const allowedProps = ['label', 'id', 'as', 'messages', 'messagesId', 'children', 'inline', 'layout', 'labelAlign', 'width', 'inputContainerRef', 'elementRef'
|
|
44
|
-
|
|
45
|
-
// added vAlign because FormField and FormFieldGroup passes it, but not adding
|
|
46
|
-
// it to allowedProps to prevent it from getting passed through accidentally
|
|
47
|
-
//'vAlign'
|
|
48
|
-
];
|
|
43
|
+
const allowedProps = ['label', 'id', 'as', 'messages', 'messagesId', 'children', 'inline', 'layout', 'labelAlign', 'width', 'inputContainerRef', 'elementRef', 'vAlign'];
|
|
49
44
|
export { propTypes, allowedProps };
|
|
@@ -22,6 +22,31 @@
|
|
|
22
22
|
* SOFTWARE.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
const generateGridLayout = (isInlineLayout, hasNewErrorMsgAndIsGroup, hasVisibleLabel, hasMessages) => {
|
|
26
|
+
if (isInlineLayout) {
|
|
27
|
+
if (hasNewErrorMsgAndIsGroup) {
|
|
28
|
+
if (hasMessages) {
|
|
29
|
+
return `${hasVisibleLabel ? ' "label messages"' : '. messages'}
|
|
30
|
+
". controls"`;
|
|
31
|
+
} else {
|
|
32
|
+
return `${hasVisibleLabel ? ' "label controls"' : '. controls'}`;
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
return `${hasVisibleLabel ? ' "label controls"' : '. controls'}
|
|
36
|
+
${hasMessages ? ' ". messages"' : ''}`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// stacked layout -- in this case we could use a simple `Flex`
|
|
40
|
+
if (hasNewErrorMsgAndIsGroup) {
|
|
41
|
+
return `${hasVisibleLabel ? ' "label"' : ''}
|
|
42
|
+
${hasMessages ? ' "messages"' : ''}
|
|
43
|
+
"controls"`;
|
|
44
|
+
} else {
|
|
45
|
+
return `${hasVisibleLabel ? ' "label"' : ''}
|
|
46
|
+
"controls"
|
|
47
|
+
${hasMessages ? ' "messages"' : ''}`;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
25
50
|
/**
|
|
26
51
|
* ---
|
|
27
52
|
* private: true
|
|
@@ -29,15 +54,55 @@
|
|
|
29
54
|
* Generates the style object from the theme and provided additional information
|
|
30
55
|
* @param {Object} componentTheme The theme variable object.
|
|
31
56
|
* @param {Object} props the props of the component, the style is applied to
|
|
32
|
-
* @param {Object}
|
|
57
|
+
* @param {Object} styleProps
|
|
33
58
|
* @return {Object} The final style object, which will be used in the component
|
|
34
59
|
*/
|
|
35
|
-
const generateStyle = (
|
|
36
|
-
const inline = props.inline
|
|
60
|
+
const generateStyle = (componentTheme, props, styleProps) => {
|
|
61
|
+
const inline = props.inline,
|
|
62
|
+
layout = props.layout,
|
|
63
|
+
vAlign = props.vAlign,
|
|
64
|
+
labelAlign = props.labelAlign;
|
|
65
|
+
const hasMessages = styleProps.hasMessages,
|
|
66
|
+
hasVisibleLabel = styleProps.hasVisibleLabel,
|
|
67
|
+
hasNewErrorMsgAndIsGroup = styleProps.hasNewErrorMsgAndIsGroup;
|
|
68
|
+
const isInlineLayout = layout === 'inline';
|
|
69
|
+
// This is quite ugly, we should simplify it
|
|
70
|
+
const gridTemplateAreas = generateGridLayout(isInlineLayout, hasNewErrorMsgAndIsGroup, hasVisibleLabel, hasMessages);
|
|
71
|
+
let gridTemplateColumns = '100%'; // stacked layout
|
|
72
|
+
if (isInlineLayout) {
|
|
73
|
+
gridTemplateColumns = '1fr 3fr';
|
|
74
|
+
if (inline) {
|
|
75
|
+
gridTemplateColumns = 'auto 3fr';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const labelStyles = {
|
|
79
|
+
all: 'initial',
|
|
80
|
+
display: 'block',
|
|
81
|
+
gridArea: 'label',
|
|
82
|
+
color: componentTheme.color,
|
|
83
|
+
fontFamily: componentTheme.fontFamily,
|
|
84
|
+
fontWeight: componentTheme.fontWeight,
|
|
85
|
+
fontSize: componentTheme.fontSize,
|
|
86
|
+
lineHeight: componentTheme.lineHeight,
|
|
87
|
+
...(isInlineLayout && {
|
|
88
|
+
margin: '0',
|
|
89
|
+
// when inline add a small padding between the label and the control
|
|
90
|
+
paddingRight: componentTheme.inlinePadding,
|
|
91
|
+
// and use the horizontal alignment prop
|
|
92
|
+
[`@media screen and (width >= ${componentTheme.stackedOrInlineBreakpoint})`]: {
|
|
93
|
+
textAlign: labelAlign
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
};
|
|
97
|
+
let alignItems = 'start';
|
|
98
|
+
if (vAlign == 'top') {
|
|
99
|
+
alignItems = 'start';
|
|
100
|
+
} else if (vAlign == 'middle') {
|
|
101
|
+
alignItems = 'center';
|
|
102
|
+
} else if (vAlign == 'bottom') {
|
|
103
|
+
alignItems = 'end';
|
|
104
|
+
}
|
|
37
105
|
return {
|
|
38
|
-
groupErrorMessage: {
|
|
39
|
-
margin: '0.5rem 0'
|
|
40
|
-
},
|
|
41
106
|
formFieldLayout: {
|
|
42
107
|
label: 'formFieldLayout',
|
|
43
108
|
all: 'initial',
|
|
@@ -48,13 +113,44 @@ const generateStyle = (_componentTheme, props) => {
|
|
|
48
113
|
direction: 'inherit',
|
|
49
114
|
textAlign: 'start',
|
|
50
115
|
opacity: 'inherit',
|
|
51
|
-
display: '
|
|
116
|
+
display: 'grid',
|
|
117
|
+
alignItems: alignItems,
|
|
118
|
+
verticalAlign: 'middle',
|
|
119
|
+
// removes margin in inline layouts
|
|
120
|
+
gridTemplateColumns: gridTemplateColumns,
|
|
121
|
+
gridTemplateAreas: gridTemplateAreas,
|
|
122
|
+
[`@media screen and (width < ${componentTheme.stackedOrInlineBreakpoint})`]: {
|
|
123
|
+
// for small screens use the stacked layout
|
|
124
|
+
gridTemplateColumns: '100%',
|
|
125
|
+
gridTemplateAreas: generateGridLayout(false, hasNewErrorMsgAndIsGroup, hasVisibleLabel, hasMessages)
|
|
126
|
+
},
|
|
127
|
+
columnGap: '0.375rem',
|
|
128
|
+
rowGap: '0.75rem',
|
|
52
129
|
width: '100%',
|
|
53
130
|
...(inline && {
|
|
54
|
-
display: 'inline-
|
|
55
|
-
verticalAlign: 'middle',
|
|
131
|
+
display: 'inline-grid',
|
|
56
132
|
width: 'auto'
|
|
57
133
|
})
|
|
134
|
+
},
|
|
135
|
+
formFieldLabel: {
|
|
136
|
+
label: 'formFieldLayout__label',
|
|
137
|
+
...labelStyles,
|
|
138
|
+
// NOTE: needs separate groups for `:is()` and `:-webkit-any()` because of css selector group validation (see https://www.w3.org/TR/selectors-3/#grouping)
|
|
139
|
+
'&:is(label)': labelStyles,
|
|
140
|
+
'&:-webkit-any(label)': labelStyles
|
|
141
|
+
},
|
|
142
|
+
formFieldChildren: {
|
|
143
|
+
label: 'formFieldLayout__children',
|
|
144
|
+
gridArea: 'controls',
|
|
145
|
+
// add a small margin between the message and the controls
|
|
146
|
+
...(hasMessages && hasNewErrorMsgAndIsGroup && {
|
|
147
|
+
marginTop: '0.375rem'
|
|
148
|
+
}),
|
|
149
|
+
...(isInlineLayout && inline && {
|
|
150
|
+
[`@media screen and (width >= ${componentTheme.stackedOrInlineBreakpoint})`]: {
|
|
151
|
+
justifySelf: 'start'
|
|
152
|
+
}
|
|
153
|
+
})
|
|
58
154
|
}
|
|
59
155
|
};
|
|
60
156
|
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The MIT License (MIT)
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2015 - present Instructure, Inc.
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Generates the theme object for the component from the theme and provided additional information
|
|
27
|
+
* @param {Object} theme The actual theme object.
|
|
28
|
+
* @return {Object} The final theme object with the overrides and component variables
|
|
29
|
+
*/
|
|
30
|
+
const generateComponentTheme = theme => {
|
|
31
|
+
const colors = theme.colors,
|
|
32
|
+
typography = theme.typography,
|
|
33
|
+
spacing = theme.spacing,
|
|
34
|
+
breakpoints = theme.breakpoints,
|
|
35
|
+
themeName = theme.key;
|
|
36
|
+
const themeSpecificStyle = {
|
|
37
|
+
canvas: {
|
|
38
|
+
color: theme['ic-brand-font-color-dark']
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const componentVariables = {
|
|
42
|
+
color: colors === null || colors === void 0 ? void 0 : colors.licorice,
|
|
43
|
+
fontFamily: typography === null || typography === void 0 ? void 0 : typography.fontFamily,
|
|
44
|
+
fontWeight: typography === null || typography === void 0 ? void 0 : typography.fontWeightBold,
|
|
45
|
+
fontSize: typography === null || typography === void 0 ? void 0 : typography.fontSizeMedium,
|
|
46
|
+
lineHeight: typography === null || typography === void 0 ? void 0 : typography.lineHeightFit,
|
|
47
|
+
inlinePadding: spacing === null || spacing === void 0 ? void 0 : spacing.xxSmall,
|
|
48
|
+
stackedOrInlineBreakpoint: breakpoints === null || breakpoints === void 0 ? void 0 : breakpoints.medium,
|
|
49
|
+
spacing: theme.spacing
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
...componentVariables,
|
|
53
|
+
...themeSpecificStyle[themeName]
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
export default generateComponentTheme;
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
import PropTypes from 'prop-types';
|
|
26
26
|
import { FormPropTypes } from '../FormPropTypes';
|
|
27
27
|
const propTypes = {
|
|
28
|
-
messages: PropTypes.arrayOf(FormPropTypes.message)
|
|
28
|
+
messages: PropTypes.arrayOf(FormPropTypes.message),
|
|
29
|
+
gridArea: PropTypes.string
|
|
29
30
|
};
|
|
30
|
-
const allowedProps = ['messages'];
|
|
31
|
+
const allowedProps = ['messages', 'gridArea'];
|
|
31
32
|
export { propTypes, allowedProps };
|
|
@@ -29,16 +29,18 @@
|
|
|
29
29
|
* Generates the style object from the theme and provided additional information
|
|
30
30
|
* @param {Object} componentTheme The theme variable object.
|
|
31
31
|
* @param {Object} props the props of the component, the style is applied to
|
|
32
|
-
* @param {Object} state the state of the component, the style is applied to
|
|
33
32
|
* @return {Object} The final style object, which will be used in the component
|
|
34
33
|
*/
|
|
35
|
-
const generateStyle = componentTheme => {
|
|
34
|
+
const generateStyle = (componentTheme, props) => {
|
|
36
35
|
return {
|
|
37
36
|
formFieldMessages: {
|
|
38
37
|
label: 'formFieldMessages',
|
|
39
38
|
padding: 0,
|
|
40
39
|
display: 'block',
|
|
41
|
-
margin: `calc(
|
|
40
|
+
margin: `calc(-${componentTheme.topMargin}) 0 0 0`,
|
|
41
|
+
...(props.gridArea && {
|
|
42
|
+
gridArea: props.gridArea
|
|
43
|
+
})
|
|
42
44
|
},
|
|
43
45
|
message: {
|
|
44
46
|
label: 'formFieldMessages__message',
|