@instructure/ui-form-field 10.12.0 → 10.12.1-snapshot-0

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.
Files changed (84) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/es/FormField/index.js +0 -1
  3. package/es/FormFieldGroup/__new-tests__/FormFieldGroup.test.js +4 -4
  4. package/es/FormFieldGroup/index.js +18 -4
  5. package/es/FormFieldLayout/__new-tests__/FormFieldLayout.test.js +10 -8
  6. package/es/FormFieldLayout/index.js +73 -54
  7. package/es/FormFieldLayout/styles.js +109 -10
  8. package/es/FormFieldLayout/theme.js +28 -2
  9. package/es/FormFieldMessages/props.js +3 -2
  10. package/es/FormFieldMessages/styles.js +5 -3
  11. package/es/FormPropTypes.js +6 -0
  12. package/es/index.js +0 -1
  13. package/lib/FormField/index.js +0 -1
  14. package/lib/FormFieldGroup/__new-tests__/FormFieldGroup.test.js +4 -4
  15. package/lib/FormFieldGroup/index.js +18 -4
  16. package/lib/FormFieldLayout/__new-tests__/FormFieldLayout.test.js +9 -7
  17. package/lib/FormFieldLayout/index.js +72 -54
  18. package/lib/FormFieldLayout/styles.js +109 -10
  19. package/lib/FormFieldLayout/theme.js +28 -2
  20. package/lib/FormFieldMessages/props.js +3 -2
  21. package/lib/FormFieldMessages/styles.js +5 -3
  22. package/lib/FormPropTypes.js +6 -0
  23. package/lib/index.js +0 -7
  24. package/package.json +15 -15
  25. package/src/FormField/README.md +31 -3
  26. package/src/FormField/index.tsx +0 -1
  27. package/src/FormFieldGroup/__new-tests__/FormFieldGroup.test.tsx +4 -6
  28. package/src/FormFieldGroup/index.tsx +41 -6
  29. package/src/FormFieldLayout/__new-tests__/FormFieldLayout.test.tsx +6 -8
  30. package/src/FormFieldLayout/index.tsx +78 -92
  31. package/src/FormFieldLayout/props.ts +28 -2
  32. package/src/FormFieldLayout/styles.ts +128 -14
  33. package/src/FormFieldLayout/theme.ts +30 -4
  34. package/src/FormFieldMessages/props.ts +8 -2
  35. package/src/FormFieldMessages/styles.ts +5 -4
  36. package/src/FormPropTypes.ts +4 -0
  37. package/src/index.ts +0 -2
  38. package/tsconfig.build.tsbuildinfo +1 -1
  39. package/types/FormField/index.d.ts.map +1 -1
  40. package/types/FormFieldGroup/index.d.ts +1 -0
  41. package/types/FormFieldGroup/index.d.ts.map +1 -1
  42. package/types/FormFieldLayout/index.d.ts +8 -7
  43. package/types/FormFieldLayout/index.d.ts.map +1 -1
  44. package/types/FormFieldLayout/props.d.ts +27 -3
  45. package/types/FormFieldLayout/props.d.ts.map +1 -1
  46. package/types/FormFieldLayout/styles.d.ts +4 -3
  47. package/types/FormFieldLayout/styles.d.ts.map +1 -1
  48. package/types/FormFieldLayout/theme.d.ts +7 -1
  49. package/types/FormFieldLayout/theme.d.ts.map +1 -1
  50. package/types/FormFieldMessages/index.d.ts +8 -2
  51. package/types/FormFieldMessages/index.d.ts.map +1 -1
  52. package/types/FormFieldMessages/props.d.ts +5 -0
  53. package/types/FormFieldMessages/props.d.ts.map +1 -1
  54. package/types/FormFieldMessages/styles.d.ts +2 -3
  55. package/types/FormFieldMessages/styles.d.ts.map +1 -1
  56. package/types/FormPropTypes.d.ts +3 -0
  57. package/types/FormPropTypes.d.ts.map +1 -1
  58. package/types/index.d.ts +0 -2
  59. package/types/index.d.ts.map +1 -1
  60. package/es/FormFieldLabel/__new-tests__/FormFieldLabel.test.js +0 -66
  61. package/es/FormFieldLabel/index.js +0 -79
  62. package/es/FormFieldLabel/props.js +0 -31
  63. package/es/FormFieldLabel/styles.js +0 -62
  64. package/es/FormFieldLabel/theme.js +0 -52
  65. package/lib/FormFieldLabel/__new-tests__/FormFieldLabel.test.js +0 -68
  66. package/lib/FormFieldLabel/index.js +0 -85
  67. package/lib/FormFieldLabel/props.js +0 -37
  68. package/lib/FormFieldLabel/styles.js +0 -68
  69. package/lib/FormFieldLabel/theme.js +0 -58
  70. package/src/FormFieldLabel/__new-tests__/FormFieldLabel.test.tsx +0 -79
  71. package/src/FormFieldLabel/index.tsx +0 -95
  72. package/src/FormFieldLabel/props.ts +0 -58
  73. package/src/FormFieldLabel/styles.ts +0 -74
  74. package/src/FormFieldLabel/theme.ts +0 -56
  75. package/types/FormFieldLabel/__new-tests__/FormFieldLabel.test.d.ts +0 -2
  76. package/types/FormFieldLabel/__new-tests__/FormFieldLabel.test.d.ts.map +0 -1
  77. package/types/FormFieldLabel/index.d.ts +0 -42
  78. package/types/FormFieldLabel/index.d.ts.map +0 -1
  79. package/types/FormFieldLabel/props.d.ts +0 -15
  80. package/types/FormFieldLabel/props.d.ts.map +0 -1
  81. package/types/FormFieldLabel/styles.d.ts +0 -15
  82. package/types/FormFieldLabel/styles.d.ts.map +0 -1
  83. package/types/FormFieldLabel/theme.d.ts +0 -10
  84. package/types/FormFieldLabel/theme.d.ts.map +0 -1
@@ -67,13 +67,17 @@ let FormFieldGroup = exports.FormFieldGroup = (_dec = (0, _emotion.withStyle)(_s
67
67
  (_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2, this.makeStylesVariables);
68
68
  }
69
69
  get makeStylesVariables() {
70
+ // new form errors dont need borders
71
+ const oldInvalid = !!this.props.messages && this.props.messages.findIndex(message => {
72
+ return message.type === 'error';
73
+ }) >= 0;
70
74
  return {
71
- invalid: this.invalid
75
+ invalid: oldInvalid
72
76
  };
73
77
  }
74
78
  get invalid() {
75
79
  return !!this.props.messages && this.props.messages.findIndex(message => {
76
- return message.type === 'error';
80
+ return message.type === 'error' || message.type === 'newError';
77
81
  }) >= 0;
78
82
  }
79
83
  renderColumns() {
@@ -105,12 +109,21 @@ let FormFieldGroup = exports.FormFieldGroup = (_dec = (0, _emotion.withStyle)(_s
105
109
  makeStyles = _this$props3.makeStyles,
106
110
  isGroup = _this$props3.isGroup,
107
111
  props = (0, _objectWithoutProperties2.default)(_this$props3, _excluded);
112
+ // This is quite ugly, but according to ARIA spec the `aria-invalid` prop
113
+ // can only be used with certain roles see
114
+ // https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-invalid#associated_roles
115
+ // `aria-invalid` is put on in FormFieldLayout because the error message
116
+ // DOM part gets there its ID.
117
+ let ariaInvalid = void 0;
118
+ 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)) {
119
+ ariaInvalid = 'true';
120
+ }
108
121
  return (0, _emotion.jsx)(_FormFieldLayout.FormFieldLayout, Object.assign({}, (0, _omitProps.omitProps)(props, FormFieldGroup.allowedProps), (0, _pickProps.pickProps)(props, _FormFieldLayout.FormFieldLayout.allowedProps), {
109
122
  vAlign: props.vAlign,
110
123
  layout: props.layout === 'inline' ? 'inline' : 'stacked',
111
124
  label: props.description,
112
125
  "aria-disabled": props.disabled ? 'true' : void 0,
113
- "aria-invalid": this.invalid ? 'true' : void 0,
126
+ "aria-invalid": ariaInvalid,
114
127
  elementRef: this.handleRef,
115
128
  isGroup: isGroup
116
129
  }), this.renderFields());
@@ -120,6 +133,7 @@ let FormFieldGroup = exports.FormFieldGroup = (_dec = (0, _emotion.withStyle)(_s
120
133
  disabled: false,
121
134
  rowSpacing: 'medium',
122
135
  colSpacing: 'small',
123
- vAlign: 'middle'
136
+ vAlign: 'middle',
137
+ isGroup: true
124
138
  }, _FormFieldGroup)) || _class);
125
139
  var _default = exports.default = FormFieldGroup;
@@ -7,7 +7,7 @@ var _vitest = require("vitest");
7
7
  var _runAxeCheck = require("@instructure/ui-axe-check/lib/runAxeCheck.js");
8
8
  require("@testing-library/jest-dom");
9
9
  var _index = require("../index");
10
- var _FormFieldLayout, _FormFieldLayout2, _input;
10
+ var _FormFieldLayout, _FormFieldLayout2;
11
11
  /*
12
12
  * The MIT License (MIT)
13
13
  *
@@ -49,7 +49,7 @@ describe('<FormFieldLayout />', () => {
49
49
  }))),
50
50
  container = _render.container;
51
51
  const formFieldLayout = container.querySelector("label[class$='-formFieldLayout']");
52
- const formFieldLabel = container.querySelector("span[class$='-formFieldLabel']");
52
+ const formFieldLabel = container.querySelector("span[class$='-formFieldLayout__label']");
53
53
  expect(formFieldLayout).toBeInTheDocument();
54
54
  expect(formFieldLabel).toBeInTheDocument();
55
55
  expect(formFieldLabel).toHaveTextContent('Username');
@@ -64,13 +64,15 @@ describe('<FormFieldLayout />', () => {
64
64
  });
65
65
  it('should provide a ref to the input container', () => {
66
66
  const inputContainerRef = _vitest.vi.fn();
67
+ const ref = /*#__PURE__*/_react.default.createRef();
67
68
  (0, _react2.render)(/*#__PURE__*/_react.default.createElement(_index.FormFieldLayout, {
68
69
  label: "Username",
69
70
  inputContainerRef: inputContainerRef
70
- }, _input || (_input = /*#__PURE__*/_react.default.createElement("input", {
71
- type: "text"
72
- }))));
73
- const input = _react2.screen.getByLabelText('Username');
74
- expect(inputContainerRef).toHaveBeenCalledWith(input.parentElement);
71
+ }, /*#__PURE__*/_react.default.createElement("input", {
72
+ type: "text",
73
+ ref: ref
74
+ })));
75
+ expect(ref.current).toBeInstanceOf(HTMLInputElement);
76
+ expect(inputContainerRef).toHaveBeenCalledWith(ref.current.parentElement);
75
77
  });
76
78
  });
@@ -8,14 +8,10 @@ exports.default = exports.FormFieldLayout = void 0;
8
8
  var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
9
9
  var _react = require("react");
10
10
  var _hasVisibleChildren = require("@instructure/ui-a11y-utils/lib/hasVisibleChildren.js");
11
- var _ScreenReaderContent = require("@instructure/ui-a11y-content/lib/ScreenReaderContent");
12
- var _Grid = require("@instructure/ui-grid/lib/Grid");
13
11
  var _omitProps = require("@instructure/ui-react-utils/lib/omitProps.js");
14
- var _pickProps = require("@instructure/ui-react-utils/lib/pickProps.js");
15
12
  var _getElementType = require("@instructure/ui-react-utils/lib/getElementType.js");
16
13
  var _withDeterministicId = require("@instructure/ui-react-utils/lib/DeterministicIdContext/withDeterministicId.js");
17
14
  var _emotion = require("@instructure/emotion");
18
- var _FormFieldLabel = require("../FormFieldLabel");
19
15
  var _FormFieldMessages = require("../FormFieldMessages");
20
16
  var _styles = _interopRequireDefault(require("./styles"));
21
17
  var _props = require("./props");
@@ -55,6 +51,7 @@ let FormFieldLayout = exports.FormFieldLayout = (_dec = (0, _withDeterministicId
55
51
  constructor(props) {
56
52
  super(props);
57
53
  this._messagesId = void 0;
54
+ this._labelId = void 0;
58
55
  this.ref = null;
59
56
  this.handleRef = el => {
60
57
  const elementRef = this.props.elementRef;
@@ -63,69 +60,96 @@ let FormFieldLayout = exports.FormFieldLayout = (_dec = (0, _withDeterministicId
63
60
  elementRef(el);
64
61
  }
65
62
  };
63
+ this.makeStyleProps = () => {
64
+ var _this$props$messages;
65
+ 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;
66
+ return {
67
+ hasMessages: this.hasMessages,
68
+ hasVisibleLabel: this.hasVisibleLabel,
69
+ // if true render error message above the controls (and below the label)
70
+ hasNewErrorMsgAndIsGroup: hasNewErrorMsgAndIsGroup
71
+ };
72
+ };
66
73
  this.handleInputContainerRef = node => {
67
74
  if (typeof this.props.inputContainerRef === 'function') {
68
75
  this.props.inputContainerRef(node);
69
76
  }
70
77
  };
71
78
  this._messagesId = props.messagesId || props.deterministicId();
79
+ this._labelId = props.deterministicId('FormField-Label');
72
80
  }
73
81
  componentDidMount() {
74
82
  var _this$props$makeStyle, _this$props;
75
- (_this$props$makeStyle = (_this$props = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props);
83
+ (_this$props$makeStyle = (_this$props = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props, this.makeStyleProps());
76
84
  }
77
85
  componentDidUpdate() {
78
86
  var _this$props$makeStyle2, _this$props2;
79
- (_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2);
87
+ (_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2, this.makeStyleProps());
80
88
  }
81
89
  get hasVisibleLabel() {
82
- return this.props.label && (0, _hasVisibleChildren.hasVisibleChildren)(this.props.label);
90
+ return this.props.label ? (0, _hasVisibleChildren.hasVisibleChildren)(this.props.label) : false;
83
91
  }
84
92
  get hasMessages() {
85
- return this.props.messages && this.props.messages.length > 0;
93
+ if (!this.props.messages || this.props.messages.length == 0) {
94
+ return false;
95
+ }
96
+ for (const msg of this.props.messages) {
97
+ if (msg.text) {
98
+ if (typeof msg.text === 'string') {
99
+ return msg.text.length > 0;
100
+ }
101
+ // this is more complicated (e.g. an array, a React component,...)
102
+ // but we don't try to optimize here for these cases
103
+ return true;
104
+ }
105
+ }
106
+ return false;
86
107
  }
87
108
  get elementType() {
88
109
  return (0, _getElementType.getElementType)(FormFieldLayout, this.props);
89
110
  }
90
- get inlineContainerAndLabel() {
91
- // Return if both the component container and label will display inline
92
- return this.props.inline && this.props.layout === 'inline';
93
- }
94
111
  renderLabel() {
95
112
  if (this.hasVisibleLabel) {
96
- return (0, _emotion.jsx)(_Grid.Grid.Col, {
97
- textAlign: this.props.labelAlign,
98
- width: this.inlineContainerAndLabel ? 'auto' : 3
99
- }, (0, _emotion.jsx)(_FormFieldLabel.FormFieldLabel, {
100
- "aria-hidden": this.elementType === 'fieldset' ? 'true' : void 0
101
- }, this.props.label));
102
- } else if (this.elementType !== 'fieldset') {
103
- // to avoid duplicate label/legend content
104
- return this.props.label;
105
- } else {
106
- return null;
107
- }
108
- }
109
- renderLegend() {
110
- // note: the legend element must be the first child of a fieldset element for SR
111
- // so we render it twice in that case (once for SR-only and one that is visible)
112
- return (0, _emotion.jsx)(_ScreenReaderContent.ScreenReaderContent, {
113
- as: "legend"
114
- }, this.props.label, this.hasMessages && (0, _emotion.jsx)(_FormFieldMessages.FormFieldMessages, {
115
- messages: this.props.messages
116
- }));
113
+ var _this$props$styles2;
114
+ if (this.elementType == 'fieldset') {
115
+ var _this$props$styles;
116
+ // `legend` has some special built in CSS, this can only be reset
117
+ // this way https://stackoverflow.com/a/65866981/319473
118
+ return (0, _emotion.jsx)("legend", {
119
+ style: {
120
+ display: 'contents'
121
+ }
122
+ }, (0, _emotion.jsx)("span", {
123
+ css: (_this$props$styles = this.props.styles) === null || _this$props$styles === void 0 ? void 0 : _this$props$styles.formFieldLabel
124
+ }, this.props.label));
125
+ }
126
+ return (0, _emotion.jsx)("span", {
127
+ css: (_this$props$styles2 = this.props.styles) === null || _this$props$styles2 === void 0 ? void 0 : _this$props$styles2.formFieldLabel
128
+ }, this.props.label);
129
+ } else if (this.props.label) {
130
+ if (this.elementType == 'fieldset') {
131
+ return (0, _emotion.jsx)("legend", {
132
+ id: this._labelId,
133
+ style: {
134
+ display: 'contents'
135
+ }
136
+ }, this.props.label);
137
+ }
138
+ // needs to be wrapped because it needs an `id`
139
+ return (0, _emotion.jsx)("div", {
140
+ id: this._labelId
141
+ }, this.props.label);
142
+ } else return null;
117
143
  }
118
144
  renderVisibleMessages() {
119
- return this.hasMessages ? (0, _emotion.jsx)(_Grid.Grid.Row, null, (0, _emotion.jsx)(_Grid.Grid.Col, {
120
- offset: this.inlineContainerAndLabel ? void 0 : 3,
121
- textAlign: this.inlineContainerAndLabel ? 'end' : void 0
122
- }, (0, _emotion.jsx)(_FormFieldMessages.FormFieldMessages, {
145
+ return this.hasMessages ? (0, _emotion.jsx)(_FormFieldMessages.FormFieldMessages, {
123
146
  id: this._messagesId,
124
- messages: this.props.messages
125
- }))) : null;
147
+ messages: this.props.messages,
148
+ gridArea: "messages"
149
+ }) : null;
126
150
  }
127
151
  render() {
128
- // any cast is needed to prevent Expression produces a union type that is too complex to represent errors
152
+ // Should be `<label>` if it's a FormField, fieldset if it's a group
129
153
  const ElementType = this.elementType;
130
154
  const _this$props3 = this.props,
131
155
  makeStyles = _this$props3.makeStyles,
@@ -134,26 +158,20 @@ let FormFieldLayout = exports.FormFieldLayout = (_dec = (0, _withDeterministicId
134
158
  isGroup = _this$props3.isGroup,
135
159
  props = (0, _objectWithoutProperties2.default)(_this$props3, _excluded);
136
160
  const width = props.width,
137
- layout = props.layout,
138
161
  children = props.children;
139
- const hasNewErrorMsg = !!(messages !== null && messages !== void 0 && messages.find(m => m.type === 'newError')) && isGroup;
140
- return (0, _emotion.jsx)(ElementType, Object.assign({}, (0, _omitProps.omitProps)(props, [...FormFieldLayout.allowedProps, ..._Grid.Grid.allowedProps]), {
162
+ const hasNewErrorMsgAndIsGroup = !!(messages !== null && messages !== void 0 && messages.find(m => m.type === 'newError')) && isGroup;
163
+ return (0, _emotion.jsx)(ElementType, Object.assign({}, (0, _omitProps.omitProps)(props, [...FormFieldLayout.allowedProps]), {
141
164
  css: styles === null || styles === void 0 ? void 0 : styles.formFieldLayout,
165
+ "aria-describedby": this.hasMessages ? this._messagesId : void 0,
166
+ "aria-errormessage": this.props['aria-invalid'] ? this._messagesId : void 0,
142
167
  style: {
143
168
  width
144
169
  },
145
- "aria-describedby": this.hasMessages ? this._messagesId : void 0,
146
170
  ref: this.handleRef
147
- }), this.elementType === 'fieldset' && this.renderLegend(), (0, _emotion.jsx)(_Grid.Grid, Object.assign({
148
- rowSpacing: "small",
149
- colSpacing: "small",
150
- startAt: layout === 'inline' && this.hasVisibleLabel ? 'medium' : null
151
- }, (0, _pickProps.pickProps)(props, _Grid.Grid.allowedProps)), (0, _emotion.jsx)(_Grid.Grid.Row, null, this.renderLabel(), (0, _emotion.jsx)(_Grid.Grid.Col, {
152
- width: this.inlineContainerAndLabel ? 'auto' : void 0,
153
- elementRef: this.handleInputContainerRef
154
- }, hasNewErrorMsg && (0, _emotion.jsx)("div", {
155
- css: styles === null || styles === void 0 ? void 0 : styles.groupErrorMessage
156
- }, this.renderVisibleMessages()), children)), !hasNewErrorMsg && this.renderVisibleMessages()));
171
+ }), this.renderLabel(), hasNewErrorMsgAndIsGroup && this.renderVisibleMessages(), (0, _emotion.jsx)("span", {
172
+ css: styles === null || styles === void 0 ? void 0 : styles.formFieldChildren,
173
+ ref: this.handleInputContainerRef
174
+ }, children), !hasNewErrorMsgAndIsGroup && this.renderVisibleMessages());
157
175
  }
158
176
  }, _FormFieldLayout.displayName = "FormFieldLayout", _FormFieldLayout.componentId = 'FormFieldLayout', _FormFieldLayout.propTypes = _props.propTypes, _FormFieldLayout.allowedProps = _props.allowedProps, _FormFieldLayout.defaultProps = {
159
177
  inline: false,
@@ -29,6 +29,31 @@ var _emotion = require("@instructure/emotion");
29
29
  * SOFTWARE.
30
30
  */
31
31
 
32
+ const generateGridLayout = (isInlineLayout, hasNewErrorMsgAndIsGroup, hasVisibleLabel, hasMessages) => {
33
+ if (isInlineLayout) {
34
+ if (hasNewErrorMsgAndIsGroup) {
35
+ if (hasMessages) {
36
+ return `${hasVisibleLabel ? ' "label messages"' : '. messages'}
37
+ ". controls"`;
38
+ } else {
39
+ return `${hasVisibleLabel ? ' "label controls"' : '. controls'}`;
40
+ }
41
+ } else {
42
+ return `${hasVisibleLabel ? ' "label controls"' : '. controls'}
43
+ ${hasMessages ? ' ". messages"' : ''}`;
44
+ }
45
+ }
46
+ // stacked layout -- in this case we could use a simple `Flex`
47
+ if (hasNewErrorMsgAndIsGroup) {
48
+ return `${hasVisibleLabel ? ' "label"' : ''}
49
+ ${hasMessages ? ' "messages"' : ''}
50
+ "controls"`;
51
+ } else {
52
+ return `${hasVisibleLabel ? ' "label"' : ''}
53
+ "controls"
54
+ ${hasMessages ? ' "messages"' : ''}`;
55
+ }
56
+ };
32
57
  /**
33
58
  * ---
34
59
  * private: true
@@ -36,18 +61,57 @@ var _emotion = require("@instructure/emotion");
36
61
  * Generates the style object from the theme and provided additional information
37
62
  * @param {Object} componentTheme The theme variable object.
38
63
  * @param {Object} props the props of the component, the style is applied to
39
- * @param {Object} state the state of the component, the style is applied to
64
+ * @param {Object} styleProps
40
65
  * @return {Object} The final style object, which will be used in the component
41
66
  */
42
- const generateStyle = (componentTheme, props) => {
67
+ const generateStyle = (componentTheme, props, styleProps) => {
43
68
  const inline = props.inline,
69
+ layout = props.layout,
70
+ vAlign = props.vAlign,
71
+ labelAlign = props.labelAlign,
44
72
  margin = props.margin;
45
- const spacing = componentTheme.spacing;
46
- const cssMargin = (0, _emotion.mapSpacingToShorthand)(margin, spacing);
73
+ const hasMessages = styleProps.hasMessages,
74
+ hasVisibleLabel = styleProps.hasVisibleLabel,
75
+ hasNewErrorMsgAndIsGroup = styleProps.hasNewErrorMsgAndIsGroup;
76
+ const cssMargin = (0, _emotion.mapSpacingToShorthand)(margin, componentTheme.spacing);
77
+ const isInlineLayout = layout === 'inline';
78
+ // This is quite ugly, we should simplify it
79
+ const gridTemplateAreas = generateGridLayout(isInlineLayout, hasNewErrorMsgAndIsGroup, hasVisibleLabel, hasMessages);
80
+ let gridTemplateColumns = '100%'; // stacked layout
81
+ if (isInlineLayout) {
82
+ gridTemplateColumns = '1fr 3fr';
83
+ if (inline) {
84
+ gridTemplateColumns = 'auto 3fr';
85
+ }
86
+ }
87
+ const labelStyles = {
88
+ all: 'initial',
89
+ display: 'block',
90
+ gridArea: 'label',
91
+ color: componentTheme.color,
92
+ fontFamily: componentTheme.fontFamily,
93
+ fontWeight: componentTheme.fontWeight,
94
+ fontSize: componentTheme.fontSize,
95
+ lineHeight: componentTheme.lineHeight,
96
+ margin: '0 0 0.75rem 0',
97
+ ...(isInlineLayout && {
98
+ // when inline add a small padding between the label and the control
99
+ paddingRight: componentTheme.inlinePadding,
100
+ // and use the horizontal alignment prop
101
+ [`@media screen and (min-width: ${componentTheme.stackedOrInlineBreakpoint})`]: {
102
+ textAlign: labelAlign
103
+ }
104
+ })
105
+ };
106
+ let alignItems = 'start';
107
+ if (vAlign == 'top') {
108
+ alignItems = 'start';
109
+ } else if (vAlign == 'middle') {
110
+ alignItems = 'center';
111
+ } else if (vAlign == 'bottom') {
112
+ alignItems = 'end';
113
+ }
47
114
  return {
48
- groupErrorMessage: {
49
- margin: '0.5rem 0'
50
- },
51
115
  formFieldLayout: {
52
116
  label: 'formFieldLayout',
53
117
  all: 'initial',
@@ -58,13 +122,48 @@ const generateStyle = (componentTheme, props) => {
58
122
  direction: 'inherit',
59
123
  textAlign: 'start',
60
124
  opacity: 'inherit',
61
- display: 'block',
125
+ display: 'grid',
126
+ alignItems: alignItems,
127
+ verticalAlign: 'middle',
128
+ // removes margin in inline layouts
129
+ gridTemplateColumns: gridTemplateColumns,
130
+ gridTemplateAreas: gridTemplateAreas,
131
+ [`@media screen and (max-width: ${componentTheme.stackedOrInlineBreakpoint})`]: {
132
+ // for small screens use the stacked layout
133
+ gridTemplateColumns: '100%',
134
+ gridTemplateAreas: generateGridLayout(false, hasNewErrorMsgAndIsGroup, hasVisibleLabel, hasMessages)
135
+ },
136
+ columnGap: '0.375rem',
62
137
  width: '100%',
63
138
  ...(inline && {
64
- display: 'inline-block',
65
- verticalAlign: 'middle',
139
+ display: 'inline-grid',
66
140
  width: 'auto'
67
141
  })
142
+ },
143
+ formFieldLabel: {
144
+ label: 'formFieldLayout__label',
145
+ ...(hasVisibleLabel && {
146
+ ...labelStyles,
147
+ // NOTE: needs separate groups for `:is()` and `:-webkit-any()` because of css selector group validation (see https://www.w3.org/TR/selectors-3/#grouping)
148
+ '&:is(label)': labelStyles,
149
+ '&:-webkit-any(label)': labelStyles
150
+ })
151
+ },
152
+ formFieldChildren: {
153
+ label: 'formFieldLayout__children',
154
+ gridArea: 'controls',
155
+ // add a small margin between the message and the controls
156
+ ...(hasMessages && hasNewErrorMsgAndIsGroup && {
157
+ marginTop: '0.375rem'
158
+ }),
159
+ ...(hasMessages && !hasNewErrorMsgAndIsGroup && {
160
+ marginBottom: '0.75rem'
161
+ }),
162
+ ...(isInlineLayout && inline && {
163
+ [`@media screen and (min-width: ${componentTheme.stackedOrInlineBreakpoint})`]: {
164
+ justifySelf: 'start'
165
+ }
166
+ })
68
167
  }
69
168
  };
70
169
  };
@@ -28,10 +28,36 @@ exports.default = void 0;
28
28
  * SOFTWARE.
29
29
  */
30
30
 
31
+ /**
32
+ * Generates the theme object for the component from the theme and provided additional information
33
+ * @param {Object} theme The actual theme object.
34
+ * @return {Object} The final theme object with the overrides and component variables
35
+ */
31
36
  const generateComponentTheme = theme => {
32
- const spacing = theme.spacing;
37
+ var _colors$contrasts;
38
+ const colors = theme.colors,
39
+ typography = theme.typography,
40
+ spacing = theme.spacing,
41
+ breakpoints = theme.breakpoints,
42
+ themeName = theme.key;
43
+ const themeSpecificStyle = {
44
+ canvas: {
45
+ color: theme['ic-brand-font-color-dark']
46
+ }
47
+ };
48
+ const componentVariables = {
49
+ color: colors === null || colors === void 0 ? void 0 : (_colors$contrasts = colors.contrasts) === null || _colors$contrasts === void 0 ? void 0 : _colors$contrasts.grey125125,
50
+ fontFamily: typography === null || typography === void 0 ? void 0 : typography.fontFamily,
51
+ fontWeight: typography === null || typography === void 0 ? void 0 : typography.fontWeightBold,
52
+ fontSize: typography === null || typography === void 0 ? void 0 : typography.fontSizeMedium,
53
+ lineHeight: typography === null || typography === void 0 ? void 0 : typography.lineHeightFit,
54
+ inlinePadding: spacing === null || spacing === void 0 ? void 0 : spacing.xxSmall,
55
+ stackedOrInlineBreakpoint: breakpoints === null || breakpoints === void 0 ? void 0 : breakpoints.medium,
56
+ spacing: theme.spacing
57
+ };
33
58
  return {
34
- spacing
59
+ ...componentVariables,
60
+ ...themeSpecificStyle[themeName]
35
61
  };
36
62
  };
37
63
  var _default = exports.default = generateComponentTheme;
@@ -32,6 +32,7 @@ var _FormPropTypes = require("../FormPropTypes");
32
32
  */
33
33
 
34
34
  const propTypes = exports.propTypes = {
35
- messages: _propTypes.default.arrayOf(_FormPropTypes.FormPropTypes.message)
35
+ messages: _propTypes.default.arrayOf(_FormPropTypes.FormPropTypes.message),
36
+ gridArea: _propTypes.default.string
36
37
  };
37
- const allowedProps = exports.allowedProps = ['messages'];
38
+ const allowedProps = exports.allowedProps = ['messages', 'gridArea'];
@@ -35,16 +35,18 @@ exports.default = void 0;
35
35
  * Generates the style object from the theme and provided additional information
36
36
  * @param {Object} componentTheme The theme variable object.
37
37
  * @param {Object} props the props of the component, the style is applied to
38
- * @param {Object} state the state of the component, the style is applied to
39
38
  * @return {Object} The final style object, which will be used in the component
40
39
  */
41
- const generateStyle = componentTheme => {
40
+ const generateStyle = (componentTheme, props) => {
42
41
  return {
43
42
  formFieldMessages: {
44
43
  label: 'formFieldMessages',
45
44
  padding: 0,
46
45
  display: 'block',
47
- margin: `calc(-1 * ${componentTheme.topMargin}) 0 0 0`
46
+ margin: `calc(-${componentTheme.topMargin}) 0 0 0`,
47
+ ...(props.gridArea && {
48
+ gridArea: props.gridArea
49
+ })
48
50
  },
49
51
  message: {
50
52
  label: 'formFieldMessages__message',
@@ -32,6 +32,12 @@ var _propTypes = _interopRequireDefault(require("prop-types"));
32
32
 
33
33
  const formMessageTypePropType = exports.formMessageTypePropType = _propTypes.default.oneOf(['error', 'newError', 'hint', 'success', 'screenreader-only']);
34
34
  const formMessageChildPropType = exports.formMessageChildPropType = _propTypes.default.node;
35
+
36
+ // TODO it will be easier if this would be just a string
37
+ /**
38
+ * The text to display in the form message
39
+ */
40
+
35
41
  /**
36
42
  * ---
37
43
  * category: utilities/form
package/lib/index.js CHANGED
@@ -15,12 +15,6 @@ Object.defineProperty(exports, "FormFieldGroup", {
15
15
  return _FormFieldGroup.FormFieldGroup;
16
16
  }
17
17
  });
18
- Object.defineProperty(exports, "FormFieldLabel", {
19
- enumerable: true,
20
- get: function () {
21
- return _FormFieldLabel.FormFieldLabel;
22
- }
23
- });
24
18
  Object.defineProperty(exports, "FormFieldLayout", {
25
19
  enumerable: true,
26
20
  get: function () {
@@ -46,7 +40,6 @@ Object.defineProperty(exports, "FormPropTypes", {
46
40
  }
47
41
  });
48
42
  var _FormField = require("./FormField");
49
- var _FormFieldLabel = require("./FormFieldLabel");
50
43
  var _FormFieldMessage = require("./FormFieldMessage");
51
44
  var _FormFieldMessages = require("./FormFieldMessages");
52
45
  var _FormFieldLayout = require("./FormFieldLayout");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-form-field",
3
- "version": "10.12.0",
3
+ "version": "10.12.1-snapshot-0",
4
4
  "description": "Form layout components.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -23,26 +23,26 @@
23
23
  },
24
24
  "license": "MIT",
25
25
  "devDependencies": {
26
- "@instructure/ui-axe-check": "10.12.0",
27
- "@instructure/ui-babel-preset": "10.12.0",
28
- "@instructure/ui-test-utils": "10.12.0",
29
- "@instructure/ui-themes": "10.12.0",
26
+ "@instructure/ui-axe-check": "10.12.1-snapshot-0",
27
+ "@instructure/ui-babel-preset": "10.12.1-snapshot-0",
28
+ "@instructure/ui-test-utils": "10.12.1-snapshot-0",
29
+ "@instructure/ui-themes": "10.12.1-snapshot-0",
30
30
  "@testing-library/jest-dom": "^6.6.3",
31
31
  "@testing-library/react": "^16.0.1",
32
32
  "vitest": "^2.1.8"
33
33
  },
34
34
  "dependencies": {
35
35
  "@babel/runtime": "^7.26.0",
36
- "@instructure/console": "10.12.0",
37
- "@instructure/emotion": "10.12.0",
38
- "@instructure/shared-types": "10.12.0",
39
- "@instructure/ui-a11y-content": "10.12.0",
40
- "@instructure/ui-a11y-utils": "10.12.0",
41
- "@instructure/ui-grid": "10.12.0",
42
- "@instructure/ui-icons": "10.12.0",
43
- "@instructure/ui-react-utils": "10.12.0",
44
- "@instructure/ui-utils": "10.12.0",
45
- "@instructure/uid": "10.12.0",
36
+ "@instructure/console": "10.12.1-snapshot-0",
37
+ "@instructure/emotion": "10.12.1-snapshot-0",
38
+ "@instructure/shared-types": "10.12.1-snapshot-0",
39
+ "@instructure/ui-a11y-content": "10.12.1-snapshot-0",
40
+ "@instructure/ui-a11y-utils": "10.12.1-snapshot-0",
41
+ "@instructure/ui-grid": "10.12.1-snapshot-0",
42
+ "@instructure/ui-icons": "10.12.1-snapshot-0",
43
+ "@instructure/ui-react-utils": "10.12.1-snapshot-0",
44
+ "@instructure/ui-utils": "10.12.1-snapshot-0",
45
+ "@instructure/uid": "10.12.1-snapshot-0",
46
46
  "prop-types": "^15.8.1"
47
47
  },
48
48
  "peerDependencies": {
@@ -9,7 +9,35 @@ components. In most cases it shouldn't be used directly.
9
9
  ---
10
10
  type: example
11
11
  ---
12
- <FormField id="_foo123" label="Opacity" width="200px">
13
- <input style={{display: 'block', width: '100%'}} id="_foo123"/>
14
- </FormField>
12
+ <div>
13
+ <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.'}]}>
15
+ <TextInput id="_foo121"/>
16
+ </FormField>
17
+ test
18
+ <hr/>
19
+ <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.'}]}>
21
+ <TextInput id="_foo122"/>
22
+ </FormField>
23
+ test
24
+ <hr/>
25
+ <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.'}]}>
27
+ <TextInput id="_foo123"/>
28
+ </FormField>
29
+ test
30
+ <hr/>
31
+ <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.'}]}>
33
+ <TextInput id="_foo124"/>
34
+ </FormField>
35
+ test
36
+ <hr/>
37
+ <FormField id="_foo121" label={<ScreenReaderContent>hidden text</ScreenReaderContent>} width="400px" layout="stacked">
38
+ <TextInput id="_foo121" />
39
+ </FormField>
40
+ test
41
+ <hr/>
42
+ </div>
15
43
  ```
@@ -68,7 +68,6 @@ class FormField extends Component<FormFieldProps> {
68
68
  label={this.props.label}
69
69
  vAlign={this.props.vAlign}
70
70
  as="label"
71
- htmlFor={this.props.id}
72
71
  elementRef={this.handleRef}
73
72
  margin={this.props.margin}
74
73
  />