@instructure/ui-text-input 11.6.1-snapshot-135 → 11.7.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.
- package/CHANGELOG.md +2 -1
- package/es/TextInput/v2/index.js +2 -59
- package/es/TextInput/v2/styles.js +2 -24
- package/lib/TextInput/v2/index.js +1 -59
- package/lib/TextInput/v2/styles.js +2 -24
- package/package.json +13 -13
- package/src/TextInput/v2/README.md +21 -14
- package/src/TextInput/v2/index.tsx +5 -90
- package/src/TextInput/v2/props.ts +1 -11
- package/src/TextInput/v2/styles.ts +3 -28
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/TextInput/v2/index.d.ts +3 -6
- package/types/TextInput/v2/index.d.ts.map +1 -1
- package/types/TextInput/v2/props.d.ts +1 -5
- package/types/TextInput/v2/props.d.ts.map +1 -1
- package/types/TextInput/v2/styles.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
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
|
-
|
|
6
|
+
# [11.7.0](https://github.com/instructure/instructure-ui/compare/v11.6.0...v11.7.0) (2026-03-18)
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
### Bug Fixes
|
|
@@ -16,6 +16,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
|
|
16
16
|
|
|
17
17
|
* **many:** add solution for using both old and new token system in the same app ([688a713](https://github.com/instructure/instructure-ui/commit/688a713ff715433bb085323dbad61285387c5141))
|
|
18
18
|
* **many:** rework TextArea and dependent FormField components ([0f8c438](https://github.com/instructure/instructure-ui/commit/0f8c43803e3458bbf7b2dd266a9e246cb89b0d3c))
|
|
19
|
+
* **ui-buttons,ui-text-input:** add condensed sizes to IconButton and simplify TextInput afterElement ([49bd675](https://github.com/instructure/instructure-ui/commit/49bd675ad9d9e77bffeb1940888d33d6cc911c60))
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
|
package/es/TextInput/v2/index.js
CHANGED
|
@@ -27,7 +27,7 @@ var _dec, _dec2, _class, _TextInput;
|
|
|
27
27
|
|
|
28
28
|
import { Component, isValidElement } from 'react';
|
|
29
29
|
import { callRenderProp, getInteraction, passthroughProps, withDeterministicId, safeCloneElement } from '@instructure/ui-react-utils';
|
|
30
|
-
import { isActiveElement, addEventListener
|
|
30
|
+
import { isActiveElement, addEventListener } from '@instructure/ui-dom-utils';
|
|
31
31
|
import { FormField } from '@instructure/ui-form-field/latest';
|
|
32
32
|
import { withStyle } from '@instructure/emotion';
|
|
33
33
|
import generateStyle from "./styles.js";
|
|
@@ -44,7 +44,6 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle),
|
|
|
44
44
|
super(props);
|
|
45
45
|
this.ref = null;
|
|
46
46
|
this._input = null;
|
|
47
|
-
this._afterElement = null;
|
|
48
47
|
this._defaultId = void 0;
|
|
49
48
|
this._messagesId = void 0;
|
|
50
49
|
this._focusListener = null;
|
|
@@ -56,14 +55,12 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle),
|
|
|
56
55
|
}
|
|
57
56
|
};
|
|
58
57
|
this.makeStyleProps = () => {
|
|
59
|
-
const afterElementHasWidth = this.state.afterElementHasWidth;
|
|
60
58
|
const beforeElement = this.props.renderBeforeInput ? callRenderProp(this.props.renderBeforeInput) : null;
|
|
61
59
|
const success = !!this.props.messages && this.props.messages.some(message => message.type === 'success');
|
|
62
60
|
return {
|
|
63
61
|
interaction: this.interaction,
|
|
64
62
|
invalid: this.invalid,
|
|
65
63
|
success: success,
|
|
66
|
-
afterElementHasWidth: afterElementHasWidth,
|
|
67
64
|
beforeElementExists: !!beforeElement
|
|
68
65
|
};
|
|
69
66
|
};
|
|
@@ -88,9 +85,6 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle),
|
|
|
88
85
|
this.props.onFocus(event);
|
|
89
86
|
}
|
|
90
87
|
};
|
|
91
|
-
this.state = {
|
|
92
|
-
afterElementHasWidth: void 0
|
|
93
|
-
};
|
|
94
88
|
this._defaultId = props.deterministicId();
|
|
95
89
|
this._messagesId = props.deterministicId('TextInput-messages');
|
|
96
90
|
}
|
|
@@ -98,11 +92,7 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle),
|
|
|
98
92
|
var _this$props$makeStyle, _this$props;
|
|
99
93
|
if (this._input) {
|
|
100
94
|
this._focusListener = addEventListener(this._input, 'focus', this.handleFocus);
|
|
101
|
-
this.setState({
|
|
102
|
-
afterElementHasWidth: this.getElementHasWidth(this._afterElement)
|
|
103
|
-
});
|
|
104
95
|
}
|
|
105
|
-
this.adjustAfterElementHeight();
|
|
106
96
|
(_this$props$makeStyle = (_this$props = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props, this.makeStyleProps());
|
|
107
97
|
}
|
|
108
98
|
componentWillUnmount() {
|
|
@@ -110,13 +100,8 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle),
|
|
|
110
100
|
this._focusListener.remove();
|
|
111
101
|
}
|
|
112
102
|
}
|
|
113
|
-
componentDidUpdate(
|
|
103
|
+
componentDidUpdate() {
|
|
114
104
|
var _this$props$makeStyle2, _this$props2;
|
|
115
|
-
if (prevProps.renderAfterInput !== this.props.renderAfterInput) {
|
|
116
|
-
this.setState({
|
|
117
|
-
afterElementHasWidth: this.getElementHasWidth(this._afterElement)
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
105
|
(_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2, this.makeStyleProps());
|
|
121
106
|
}
|
|
122
107
|
renderInstUIIcon(elementToRender) {
|
|
@@ -139,24 +124,6 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle),
|
|
|
139
124
|
}
|
|
140
125
|
return rendered;
|
|
141
126
|
}
|
|
142
|
-
adjustAfterElementHeight() {
|
|
143
|
-
var _this$_afterElement, _afterElementChild$fi;
|
|
144
|
-
const afterElementChild = (_this$_afterElement = this._afterElement) === null || _this$_afterElement === void 0 ? void 0 : _this$_afterElement.firstElementChild;
|
|
145
|
-
|
|
146
|
-
// Check if the child is a button, then get the button's first child (the content span)
|
|
147
|
-
const buttonContentSpan = (afterElementChild === null || afterElementChild === void 0 ? void 0 : afterElementChild.tagName) === 'BUTTON' ? afterElementChild.firstElementChild : null;
|
|
148
|
-
|
|
149
|
-
// This is a necessary workaround for DateInput2 because it uses a Popover, which has a nested Button as an afterElement
|
|
150
|
-
// Check if the child is a Popover's inner span containing a button, then get the button's first child (the content span)
|
|
151
|
-
const popoverContentSpan = (afterElementChild === null || afterElementChild === void 0 ? void 0 : afterElementChild.tagName) === 'SPAN' && ((_afterElementChild$fi = afterElementChild.firstElementChild) === null || _afterElementChild$fi === void 0 ? void 0 : _afterElementChild$fi.tagName) === 'BUTTON' ? afterElementChild.firstElementChild.firstElementChild : null;
|
|
152
|
-
const targetSpan = buttonContentSpan !== null && buttonContentSpan !== void 0 ? buttonContentSpan : popoverContentSpan;
|
|
153
|
-
if (targetSpan) {
|
|
154
|
-
// Set the height to 36px (the height of a medium TextInput) to avoid layout shift when the afterElement content changes
|
|
155
|
-
// this temporary workaround is necessary because otherwise the layout breaks, later on IconButton's default height will be adjusted to the TextInput size
|
|
156
|
-
// so this workaround will not be needed anymore
|
|
157
|
-
targetSpan.style.height = '36px';
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
127
|
focus() {
|
|
161
128
|
var _this$_input;
|
|
162
129
|
(_this$_input = this._input) === null || _this$_input === void 0 ? void 0 : _this$_input.focus();
|
|
@@ -223,27 +190,6 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle),
|
|
|
223
190
|
onBlur: this.handleBlur
|
|
224
191
|
});
|
|
225
192
|
}
|
|
226
|
-
getElementHasWidth(element) {
|
|
227
|
-
if (!element) {
|
|
228
|
-
return void 0;
|
|
229
|
-
}
|
|
230
|
-
const computedStyle = getCSSStyleDeclaration(element);
|
|
231
|
-
if (!computedStyle) {
|
|
232
|
-
return void 0;
|
|
233
|
-
}
|
|
234
|
-
const width = computedStyle.width,
|
|
235
|
-
paddingInlineStart = computedStyle.paddingInlineStart,
|
|
236
|
-
paddingInlineEnd = computedStyle.paddingInlineEnd;
|
|
237
|
-
if (width === 'auto' || width === '') {
|
|
238
|
-
// This is a workaround for an edge-case, when the TextInput's parent
|
|
239
|
-
// is hidden on load, so the element is not visible either.
|
|
240
|
-
// In this case the computed width is going to be either 'auto' or '',
|
|
241
|
-
// so we assume it has width so that the padding won't be removed.
|
|
242
|
-
return true;
|
|
243
|
-
}
|
|
244
|
-
const elementWidth = parseFloat(width) - parseFloat(paddingInlineStart) - parseFloat(paddingInlineEnd);
|
|
245
|
-
return elementWidth > 0;
|
|
246
|
-
}
|
|
247
193
|
render() {
|
|
248
194
|
const _this$props4 = this.props,
|
|
249
195
|
width = _this$props4.width,
|
|
@@ -282,9 +228,6 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle),
|
|
|
282
228
|
css: styles === null || styles === void 0 ? void 0 : styles.inputLayout,
|
|
283
229
|
children: [this.renderInput(), afterElement && _jsx("span", {
|
|
284
230
|
css: styles === null || styles === void 0 ? void 0 : styles.afterElement,
|
|
285
|
-
ref: e => {
|
|
286
|
-
this._afterElement = e;
|
|
287
|
-
},
|
|
288
231
|
children: afterElement
|
|
289
232
|
})]
|
|
290
233
|
})]
|
|
@@ -42,7 +42,6 @@ const generateStyle = (componentTheme, props, sharedTokens, state) => {
|
|
|
42
42
|
const interaction = state.interaction,
|
|
43
43
|
success = state.success,
|
|
44
44
|
invalid = state.invalid,
|
|
45
|
-
afterElementHasWidth = state.afterElementHasWidth,
|
|
46
45
|
beforeElementExists = state.beforeElementExists;
|
|
47
46
|
const sizeVariants = {
|
|
48
47
|
small: {
|
|
@@ -200,34 +199,13 @@ const generateStyle = (componentTheme, props, sharedTokens, state) => {
|
|
|
200
199
|
flexDirection: 'row'
|
|
201
200
|
},
|
|
202
201
|
afterElement: {
|
|
203
|
-
// the next couple lines (until the `label`) is needed so the IconButton looks OK inside the TextInput
|
|
204
|
-
// explanation: if the content inside is not a button or a popover (which could contain a button) it should have some padding on the right
|
|
205
|
-
// lineHeight is only needed if it is not popover or button
|
|
206
|
-
'& > :not(button):not([data-position^="Popover"])': {
|
|
207
|
-
marginRight: paddingHorizontalVariants[size]
|
|
208
|
-
// TODO check if it looks OK with the new buttons. With this it does not look OK with new icons
|
|
209
|
-
//...(sizeVariants[size!] && {
|
|
210
|
-
// lineHeight: sizeVariants[size!]?.lineHeight
|
|
211
|
-
//})
|
|
212
|
-
},
|
|
213
202
|
display: 'flex',
|
|
214
203
|
alignItems: 'center',
|
|
215
|
-
|
|
216
|
-
...(sizeVariants[size] ?
|
|
217
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
218
|
-
(({
|
|
219
|
-
lineHeight,
|
|
220
|
-
...rest
|
|
221
|
-
}) => rest)(sizeVariants[size]) : {}),
|
|
204
|
+
paddingInlineEnd: paddingHorizontalVariants[size],
|
|
222
205
|
label: 'textInput__afterElement',
|
|
223
206
|
...viewBase,
|
|
224
207
|
borderRadius: componentTheme.borderRadius,
|
|
225
|
-
flexShrink: 0
|
|
226
|
-
// we only override the padding once the width is calculated,
|
|
227
|
-
// it needs the padding on render
|
|
228
|
-
...(afterElementHasWidth === false && {
|
|
229
|
-
paddingInlineEnd: 0
|
|
230
|
-
})
|
|
208
|
+
flexShrink: 0
|
|
231
209
|
}
|
|
232
210
|
};
|
|
233
211
|
};
|
|
@@ -14,7 +14,6 @@ var _withDeterministicId = require("@instructure/ui-react-utils/lib/Deterministi
|
|
|
14
14
|
var _safeCloneElement = require("@instructure/ui-react-utils/lib/safeCloneElement.js");
|
|
15
15
|
var _isActiveElement = require("@instructure/ui-dom-utils/lib/isActiveElement.js");
|
|
16
16
|
var _addEventListener = require("@instructure/ui-dom-utils/lib/addEventListener.js");
|
|
17
|
-
var _getCSSStyleDeclaration = require("@instructure/ui-dom-utils/lib/getCSSStyleDeclaration.js");
|
|
18
17
|
var _latest = require("@instructure/ui-form-field/latest");
|
|
19
18
|
var _emotion = require("@instructure/emotion");
|
|
20
19
|
var _styles = _interopRequireDefault(require("./styles"));
|
|
@@ -56,7 +55,6 @@ let TextInput = exports.TextInput = (_dec = (0, _withDeterministicId.withDetermi
|
|
|
56
55
|
super(props);
|
|
57
56
|
this.ref = null;
|
|
58
57
|
this._input = null;
|
|
59
|
-
this._afterElement = null;
|
|
60
58
|
this._defaultId = void 0;
|
|
61
59
|
this._messagesId = void 0;
|
|
62
60
|
this._focusListener = null;
|
|
@@ -68,14 +66,12 @@ let TextInput = exports.TextInput = (_dec = (0, _withDeterministicId.withDetermi
|
|
|
68
66
|
}
|
|
69
67
|
};
|
|
70
68
|
this.makeStyleProps = () => {
|
|
71
|
-
const afterElementHasWidth = this.state.afterElementHasWidth;
|
|
72
69
|
const beforeElement = this.props.renderBeforeInput ? (0, _callRenderProp.callRenderProp)(this.props.renderBeforeInput) : null;
|
|
73
70
|
const success = !!this.props.messages && this.props.messages.some(message => message.type === 'success');
|
|
74
71
|
return {
|
|
75
72
|
interaction: this.interaction,
|
|
76
73
|
invalid: this.invalid,
|
|
77
74
|
success: success,
|
|
78
|
-
afterElementHasWidth: afterElementHasWidth,
|
|
79
75
|
beforeElementExists: !!beforeElement
|
|
80
76
|
};
|
|
81
77
|
};
|
|
@@ -100,9 +96,6 @@ let TextInput = exports.TextInput = (_dec = (0, _withDeterministicId.withDetermi
|
|
|
100
96
|
this.props.onFocus(event);
|
|
101
97
|
}
|
|
102
98
|
};
|
|
103
|
-
this.state = {
|
|
104
|
-
afterElementHasWidth: void 0
|
|
105
|
-
};
|
|
106
99
|
this._defaultId = props.deterministicId();
|
|
107
100
|
this._messagesId = props.deterministicId('TextInput-messages');
|
|
108
101
|
}
|
|
@@ -110,11 +103,7 @@ let TextInput = exports.TextInput = (_dec = (0, _withDeterministicId.withDetermi
|
|
|
110
103
|
var _this$props$makeStyle, _this$props;
|
|
111
104
|
if (this._input) {
|
|
112
105
|
this._focusListener = (0, _addEventListener.addEventListener)(this._input, 'focus', this.handleFocus);
|
|
113
|
-
this.setState({
|
|
114
|
-
afterElementHasWidth: this.getElementHasWidth(this._afterElement)
|
|
115
|
-
});
|
|
116
106
|
}
|
|
117
|
-
this.adjustAfterElementHeight();
|
|
118
107
|
(_this$props$makeStyle = (_this$props = this.props).makeStyles) === null || _this$props$makeStyle === void 0 ? void 0 : _this$props$makeStyle.call(_this$props, this.makeStyleProps());
|
|
119
108
|
}
|
|
120
109
|
componentWillUnmount() {
|
|
@@ -122,13 +111,8 @@ let TextInput = exports.TextInput = (_dec = (0, _withDeterministicId.withDetermi
|
|
|
122
111
|
this._focusListener.remove();
|
|
123
112
|
}
|
|
124
113
|
}
|
|
125
|
-
componentDidUpdate(
|
|
114
|
+
componentDidUpdate() {
|
|
126
115
|
var _this$props$makeStyle2, _this$props2;
|
|
127
|
-
if (prevProps.renderAfterInput !== this.props.renderAfterInput) {
|
|
128
|
-
this.setState({
|
|
129
|
-
afterElementHasWidth: this.getElementHasWidth(this._afterElement)
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
116
|
(_this$props$makeStyle2 = (_this$props2 = this.props).makeStyles) === null || _this$props$makeStyle2 === void 0 ? void 0 : _this$props$makeStyle2.call(_this$props2, this.makeStyleProps());
|
|
133
117
|
}
|
|
134
118
|
renderInstUIIcon(elementToRender) {
|
|
@@ -151,24 +135,6 @@ let TextInput = exports.TextInput = (_dec = (0, _withDeterministicId.withDetermi
|
|
|
151
135
|
}
|
|
152
136
|
return rendered;
|
|
153
137
|
}
|
|
154
|
-
adjustAfterElementHeight() {
|
|
155
|
-
var _this$_afterElement, _afterElementChild$fi;
|
|
156
|
-
const afterElementChild = (_this$_afterElement = this._afterElement) === null || _this$_afterElement === void 0 ? void 0 : _this$_afterElement.firstElementChild;
|
|
157
|
-
|
|
158
|
-
// Check if the child is a button, then get the button's first child (the content span)
|
|
159
|
-
const buttonContentSpan = (afterElementChild === null || afterElementChild === void 0 ? void 0 : afterElementChild.tagName) === 'BUTTON' ? afterElementChild.firstElementChild : null;
|
|
160
|
-
|
|
161
|
-
// This is a necessary workaround for DateInput2 because it uses a Popover, which has a nested Button as an afterElement
|
|
162
|
-
// Check if the child is a Popover's inner span containing a button, then get the button's first child (the content span)
|
|
163
|
-
const popoverContentSpan = (afterElementChild === null || afterElementChild === void 0 ? void 0 : afterElementChild.tagName) === 'SPAN' && ((_afterElementChild$fi = afterElementChild.firstElementChild) === null || _afterElementChild$fi === void 0 ? void 0 : _afterElementChild$fi.tagName) === 'BUTTON' ? afterElementChild.firstElementChild.firstElementChild : null;
|
|
164
|
-
const targetSpan = buttonContentSpan !== null && buttonContentSpan !== void 0 ? buttonContentSpan : popoverContentSpan;
|
|
165
|
-
if (targetSpan) {
|
|
166
|
-
// Set the height to 36px (the height of a medium TextInput) to avoid layout shift when the afterElement content changes
|
|
167
|
-
// this temporary workaround is necessary because otherwise the layout breaks, later on IconButton's default height will be adjusted to the TextInput size
|
|
168
|
-
// so this workaround will not be needed anymore
|
|
169
|
-
targetSpan.style.height = '36px';
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
138
|
focus() {
|
|
173
139
|
var _this$_input;
|
|
174
140
|
(_this$_input = this._input) === null || _this$_input === void 0 ? void 0 : _this$_input.focus();
|
|
@@ -235,27 +201,6 @@ let TextInput = exports.TextInput = (_dec = (0, _withDeterministicId.withDetermi
|
|
|
235
201
|
onBlur: this.handleBlur
|
|
236
202
|
});
|
|
237
203
|
}
|
|
238
|
-
getElementHasWidth(element) {
|
|
239
|
-
if (!element) {
|
|
240
|
-
return void 0;
|
|
241
|
-
}
|
|
242
|
-
const computedStyle = (0, _getCSSStyleDeclaration.getCSSStyleDeclaration)(element);
|
|
243
|
-
if (!computedStyle) {
|
|
244
|
-
return void 0;
|
|
245
|
-
}
|
|
246
|
-
const width = computedStyle.width,
|
|
247
|
-
paddingInlineStart = computedStyle.paddingInlineStart,
|
|
248
|
-
paddingInlineEnd = computedStyle.paddingInlineEnd;
|
|
249
|
-
if (width === 'auto' || width === '') {
|
|
250
|
-
// This is a workaround for an edge-case, when the TextInput's parent
|
|
251
|
-
// is hidden on load, so the element is not visible either.
|
|
252
|
-
// In this case the computed width is going to be either 'auto' or '',
|
|
253
|
-
// so we assume it has width so that the padding won't be removed.
|
|
254
|
-
return true;
|
|
255
|
-
}
|
|
256
|
-
const elementWidth = parseFloat(width) - parseFloat(paddingInlineStart) - parseFloat(paddingInlineEnd);
|
|
257
|
-
return elementWidth > 0;
|
|
258
|
-
}
|
|
259
204
|
render() {
|
|
260
205
|
const _this$props4 = this.props,
|
|
261
206
|
width = _this$props4.width,
|
|
@@ -294,9 +239,6 @@ let TextInput = exports.TextInput = (_dec = (0, _withDeterministicId.withDetermi
|
|
|
294
239
|
css: styles === null || styles === void 0 ? void 0 : styles.inputLayout,
|
|
295
240
|
children: [this.renderInput(), afterElement && (0, _jsxRuntime.jsx)("span", {
|
|
296
241
|
css: styles === null || styles === void 0 ? void 0 : styles.afterElement,
|
|
297
|
-
ref: e => {
|
|
298
|
-
this._afterElement = e;
|
|
299
|
-
},
|
|
300
242
|
children: afterElement
|
|
301
243
|
})]
|
|
302
244
|
})]
|
|
@@ -47,7 +47,6 @@ const generateStyle = (componentTheme, props, sharedTokens, state) => {
|
|
|
47
47
|
const interaction = state.interaction,
|
|
48
48
|
success = state.success,
|
|
49
49
|
invalid = state.invalid,
|
|
50
|
-
afterElementHasWidth = state.afterElementHasWidth,
|
|
51
50
|
beforeElementExists = state.beforeElementExists;
|
|
52
51
|
const sizeVariants = {
|
|
53
52
|
small: {
|
|
@@ -205,34 +204,13 @@ const generateStyle = (componentTheme, props, sharedTokens, state) => {
|
|
|
205
204
|
flexDirection: 'row'
|
|
206
205
|
},
|
|
207
206
|
afterElement: {
|
|
208
|
-
// the next couple lines (until the `label`) is needed so the IconButton looks OK inside the TextInput
|
|
209
|
-
// explanation: if the content inside is not a button or a popover (which could contain a button) it should have some padding on the right
|
|
210
|
-
// lineHeight is only needed if it is not popover or button
|
|
211
|
-
'& > :not(button):not([data-position^="Popover"])': {
|
|
212
|
-
marginRight: paddingHorizontalVariants[size]
|
|
213
|
-
// TODO check if it looks OK with the new buttons. With this it does not look OK with new icons
|
|
214
|
-
//...(sizeVariants[size!] && {
|
|
215
|
-
// lineHeight: sizeVariants[size!]?.lineHeight
|
|
216
|
-
//})
|
|
217
|
-
},
|
|
218
207
|
display: 'flex',
|
|
219
208
|
alignItems: 'center',
|
|
220
|
-
|
|
221
|
-
...(sizeVariants[size] ?
|
|
222
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
223
|
-
(({
|
|
224
|
-
lineHeight,
|
|
225
|
-
...rest
|
|
226
|
-
}) => rest)(sizeVariants[size]) : {}),
|
|
209
|
+
paddingInlineEnd: paddingHorizontalVariants[size],
|
|
227
210
|
label: 'textInput__afterElement',
|
|
228
211
|
...viewBase,
|
|
229
212
|
borderRadius: componentTheme.borderRadius,
|
|
230
|
-
flexShrink: 0
|
|
231
|
-
// we only override the padding once the width is calculated,
|
|
232
|
-
// it needs the padding on render
|
|
233
|
-
...(afterElementHasWidth === false && {
|
|
234
|
-
paddingInlineEnd: 0
|
|
235
|
-
})
|
|
213
|
+
flexShrink: 0
|
|
236
214
|
}
|
|
237
215
|
};
|
|
238
216
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instructure/ui-text-input",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.7.0",
|
|
4
4
|
"description": "A styled HTML text input component.",
|
|
5
5
|
"author": "Instructure, Inc. Engineering and Product Design",
|
|
6
6
|
"module": "./es/index.js",
|
|
@@ -18,21 +18,21 @@
|
|
|
18
18
|
"@testing-library/react": "15.0.7",
|
|
19
19
|
"@testing-library/user-event": "^14.6.1",
|
|
20
20
|
"vitest": "^3.2.2",
|
|
21
|
-
"@instructure/ui-
|
|
22
|
-
"@instructure/ui-
|
|
23
|
-
"@instructure/ui-badge": "11.
|
|
24
|
-
"@instructure/ui-color-utils": "11.
|
|
21
|
+
"@instructure/ui-babel-preset": "11.7.0",
|
|
22
|
+
"@instructure/ui-axe-check": "11.7.0",
|
|
23
|
+
"@instructure/ui-badge": "11.7.0",
|
|
24
|
+
"@instructure/ui-color-utils": "11.7.0"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@babel/runtime": "^7.27.6",
|
|
28
|
-
"@instructure/emotion": "11.
|
|
29
|
-
"@instructure/
|
|
30
|
-
"@instructure/ui-
|
|
31
|
-
"@instructure/ui-a11y-utils": "11.
|
|
32
|
-
"@instructure/
|
|
33
|
-
"@instructure/ui-
|
|
34
|
-
"@instructure/ui-tag": "11.
|
|
35
|
-
"@instructure/ui-
|
|
28
|
+
"@instructure/emotion": "11.7.0",
|
|
29
|
+
"@instructure/ui-dom-utils": "11.7.0",
|
|
30
|
+
"@instructure/ui-form-field": "11.7.0",
|
|
31
|
+
"@instructure/ui-a11y-utils": "11.7.0",
|
|
32
|
+
"@instructure/shared-types": "11.7.0",
|
|
33
|
+
"@instructure/ui-themes": "11.7.0",
|
|
34
|
+
"@instructure/ui-tag": "11.7.0",
|
|
35
|
+
"@instructure/ui-react-utils": "11.7.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"react": ">=18 <=19"
|
|
@@ -164,7 +164,7 @@ type: example
|
|
|
164
164
|
render(<ExtraContentExample />)
|
|
165
165
|
```
|
|
166
166
|
|
|
167
|
-
Another common usecase is to add an
|
|
167
|
+
Another common usecase is to add an [IconButton](IconButton) at the end of a TextInput, e.g. for revealing the content of a password field. Use the `condensedMedium` size together with `withBorder={false}` and `withBackground={false}` on the IconButton.
|
|
168
168
|
|
|
169
169
|
```js
|
|
170
170
|
---
|
|
@@ -174,18 +174,25 @@ const InputsWithButtonsExample = () => {
|
|
|
174
174
|
const [passwordValue, setPasswordValue] = useState('')
|
|
175
175
|
const [showPassword, setShowPassword] = useState(false)
|
|
176
176
|
return (
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
177
|
+
<View as="div" maxWidth="20rem">
|
|
178
|
+
<TextInput
|
|
179
|
+
renderLabel="Password"
|
|
180
|
+
type={showPassword ? 'text' : 'password'}
|
|
181
|
+
value={passwordValue}
|
|
182
|
+
onChange={(e, newValue) => setPasswordValue(newValue)}
|
|
183
|
+
renderAfterInput={
|
|
184
|
+
<IconButton
|
|
185
|
+
size="condensedMedium"
|
|
186
|
+
withBorder={false}
|
|
187
|
+
withBackground={false}
|
|
188
|
+
screenReaderLabel={showPassword ? 'Hide password' : 'Show password'}
|
|
189
|
+
onClick={() => setShowPassword((prev) => !prev)}
|
|
190
|
+
>
|
|
191
|
+
{showPassword ? <EyeOffInstUIIcon /> : <EyeInstUIIcon />}
|
|
192
|
+
</IconButton>
|
|
193
|
+
}
|
|
194
|
+
/>
|
|
195
|
+
</View>
|
|
189
196
|
)
|
|
190
197
|
}
|
|
191
198
|
render(<InputsWithButtonsExample />)
|
|
@@ -228,7 +235,7 @@ type: example
|
|
|
228
235
|
<View as="div" maxWidth="250px">
|
|
229
236
|
<TextInput
|
|
230
237
|
renderLabel="I will not wrap"
|
|
231
|
-
renderBeforeInput={
|
|
238
|
+
renderBeforeInput={<SearchInstUIIcon />}
|
|
232
239
|
renderAfterInput={<Avatar name="Paula Panda" src={avatarSquare} size="x-small" />}
|
|
233
240
|
shouldNotWrap
|
|
234
241
|
/>
|
|
@@ -31,20 +31,12 @@ import {
|
|
|
31
31
|
withDeterministicId,
|
|
32
32
|
safeCloneElement
|
|
33
33
|
} from '@instructure/ui-react-utils'
|
|
34
|
-
import {
|
|
35
|
-
isActiveElement,
|
|
36
|
-
addEventListener,
|
|
37
|
-
getCSSStyleDeclaration
|
|
38
|
-
} from '@instructure/ui-dom-utils'
|
|
34
|
+
import { isActiveElement, addEventListener } from '@instructure/ui-dom-utils'
|
|
39
35
|
import { FormField } from '@instructure/ui-form-field/latest'
|
|
40
36
|
import { withStyle } from '@instructure/emotion'
|
|
41
37
|
|
|
42
38
|
import generateStyle from './styles'
|
|
43
|
-
import type {
|
|
44
|
-
TextInputProps,
|
|
45
|
-
TextInputState,
|
|
46
|
-
TextInputStyleProps
|
|
47
|
-
} from './props'
|
|
39
|
+
import type { TextInputProps, TextInputStyleProps } from './props'
|
|
48
40
|
import { allowedProps } from './props'
|
|
49
41
|
import type { Renderable } from '@instructure/shared-types'
|
|
50
42
|
|
|
@@ -56,7 +48,7 @@ tags: form, field, input
|
|
|
56
48
|
**/
|
|
57
49
|
@withDeterministicId()
|
|
58
50
|
@withStyle(generateStyle)
|
|
59
|
-
class TextInput extends Component<TextInputProps
|
|
51
|
+
class TextInput extends Component<TextInputProps> {
|
|
60
52
|
static readonly componentId = 'TextInput'
|
|
61
53
|
|
|
62
54
|
static allowedProps = allowedProps
|
|
@@ -75,7 +67,6 @@ class TextInput extends Component<TextInputProps, TextInputState> {
|
|
|
75
67
|
|
|
76
68
|
constructor(props: TextInputProps) {
|
|
77
69
|
super(props)
|
|
78
|
-
this.state = { afterElementHasWidth: undefined }
|
|
79
70
|
this._defaultId = props.deterministicId!()
|
|
80
71
|
this._messagesId = props.deterministicId!('TextInput-messages')
|
|
81
72
|
}
|
|
@@ -83,7 +74,6 @@ class TextInput extends Component<TextInputProps, TextInputState> {
|
|
|
83
74
|
ref: Element | null = null
|
|
84
75
|
|
|
85
76
|
private _input: HTMLInputElement | null = null
|
|
86
|
-
private _afterElement: HTMLSpanElement | null = null
|
|
87
77
|
|
|
88
78
|
private readonly _defaultId: string
|
|
89
79
|
private readonly _messagesId: string
|
|
@@ -107,11 +97,7 @@ class TextInput extends Component<TextInputProps, TextInputState> {
|
|
|
107
97
|
'focus',
|
|
108
98
|
this.handleFocus
|
|
109
99
|
)
|
|
110
|
-
this.setState({
|
|
111
|
-
afterElementHasWidth: this.getElementHasWidth(this._afterElement)
|
|
112
|
-
})
|
|
113
100
|
}
|
|
114
|
-
this.adjustAfterElementHeight()
|
|
115
101
|
this.props.makeStyles?.(this.makeStyleProps())
|
|
116
102
|
}
|
|
117
103
|
|
|
@@ -121,12 +107,7 @@ class TextInput extends Component<TextInputProps, TextInputState> {
|
|
|
121
107
|
}
|
|
122
108
|
}
|
|
123
109
|
|
|
124
|
-
componentDidUpdate(
|
|
125
|
-
if (prevProps.renderAfterInput !== this.props.renderAfterInput) {
|
|
126
|
-
this.setState({
|
|
127
|
-
afterElementHasWidth: this.getElementHasWidth(this._afterElement)
|
|
128
|
-
})
|
|
129
|
-
}
|
|
110
|
+
componentDidUpdate() {
|
|
130
111
|
this.props.makeStyles?.(this.makeStyleProps())
|
|
131
112
|
}
|
|
132
113
|
|
|
@@ -152,37 +133,7 @@ class TextInput extends Component<TextInputProps, TextInputState> {
|
|
|
152
133
|
return rendered
|
|
153
134
|
}
|
|
154
135
|
|
|
155
|
-
adjustAfterElementHeight() {
|
|
156
|
-
const afterElementChild = this._afterElement
|
|
157
|
-
?.firstElementChild as HTMLElement | null
|
|
158
|
-
|
|
159
|
-
// Check if the child is a button, then get the button's first child (the content span)
|
|
160
|
-
const buttonContentSpan =
|
|
161
|
-
afterElementChild?.tagName === 'BUTTON'
|
|
162
|
-
? (afterElementChild.firstElementChild as HTMLElement | null)
|
|
163
|
-
: null
|
|
164
|
-
|
|
165
|
-
// This is a necessary workaround for DateInput2 because it uses a Popover, which has a nested Button as an afterElement
|
|
166
|
-
// Check if the child is a Popover's inner span containing a button, then get the button's first child (the content span)
|
|
167
|
-
const popoverContentSpan =
|
|
168
|
-
afterElementChild?.tagName === 'SPAN' &&
|
|
169
|
-
afterElementChild.firstElementChild?.tagName === 'BUTTON'
|
|
170
|
-
? (afterElementChild.firstElementChild
|
|
171
|
-
.firstElementChild as HTMLElement | null)
|
|
172
|
-
: null
|
|
173
|
-
|
|
174
|
-
const targetSpan = buttonContentSpan ?? popoverContentSpan
|
|
175
|
-
|
|
176
|
-
if (targetSpan) {
|
|
177
|
-
// Set the height to 36px (the height of a medium TextInput) to avoid layout shift when the afterElement content changes
|
|
178
|
-
// this temporary workaround is necessary because otherwise the layout breaks, later on IconButton's default height will be adjusted to the TextInput size
|
|
179
|
-
// so this workaround will not be needed anymore
|
|
180
|
-
targetSpan.style.height = '36px'
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
136
|
makeStyleProps = (): TextInputStyleProps => {
|
|
185
|
-
const { afterElementHasWidth } = this.state
|
|
186
137
|
const beforeElement = this.props.renderBeforeInput
|
|
187
138
|
? callRenderProp(this.props.renderBeforeInput)
|
|
188
139
|
: null
|
|
@@ -193,7 +144,6 @@ class TextInput extends Component<TextInputProps, TextInputState> {
|
|
|
193
144
|
interaction: this.interaction,
|
|
194
145
|
invalid: this.invalid,
|
|
195
146
|
success: success,
|
|
196
|
-
afterElementHasWidth: afterElementHasWidth,
|
|
197
147
|
beforeElementExists: !!beforeElement
|
|
198
148
|
}
|
|
199
149
|
}
|
|
@@ -303,34 +253,6 @@ class TextInput extends Component<TextInputProps, TextInputState> {
|
|
|
303
253
|
)
|
|
304
254
|
}
|
|
305
255
|
|
|
306
|
-
getElementHasWidth(element: Element | null) {
|
|
307
|
-
if (!element) {
|
|
308
|
-
return undefined
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const computedStyle = getCSSStyleDeclaration(element)
|
|
312
|
-
if (!computedStyle) {
|
|
313
|
-
return undefined
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const { width, paddingInlineStart, paddingInlineEnd } = computedStyle
|
|
317
|
-
|
|
318
|
-
if (width === 'auto' || width === '') {
|
|
319
|
-
// This is a workaround for an edge-case, when the TextInput's parent
|
|
320
|
-
// is hidden on load, so the element is not visible either.
|
|
321
|
-
// In this case the computed width is going to be either 'auto' or '',
|
|
322
|
-
// so we assume it has width so that the padding won't be removed.
|
|
323
|
-
return true
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const elementWidth =
|
|
327
|
-
parseFloat(width) -
|
|
328
|
-
parseFloat(paddingInlineStart) -
|
|
329
|
-
parseFloat(paddingInlineEnd)
|
|
330
|
-
|
|
331
|
-
return elementWidth > 0
|
|
332
|
-
}
|
|
333
|
-
|
|
334
256
|
render() {
|
|
335
257
|
const {
|
|
336
258
|
width,
|
|
@@ -381,14 +303,7 @@ class TextInput extends Component<TextInputProps, TextInputState> {
|
|
|
381
303
|
<span css={styles?.inputLayout}>
|
|
382
304
|
{this.renderInput()}
|
|
383
305
|
{afterElement && (
|
|
384
|
-
<span
|
|
385
|
-
css={styles?.afterElement}
|
|
386
|
-
ref={(e) => {
|
|
387
|
-
this._afterElement = e
|
|
388
|
-
}}
|
|
389
|
-
>
|
|
390
|
-
{afterElement}
|
|
391
|
-
</span>
|
|
306
|
+
<span css={styles?.afterElement}>{afterElement}</span>
|
|
392
307
|
)}
|
|
393
308
|
</span>
|
|
394
309
|
</span>
|
|
@@ -223,22 +223,12 @@ const allowedProps: AllowedPropKeys = [
|
|
|
223
223
|
'margin'
|
|
224
224
|
]
|
|
225
225
|
|
|
226
|
-
type TextInputState = {
|
|
227
|
-
afterElementHasWidth?: boolean
|
|
228
|
-
}
|
|
229
|
-
|
|
230
226
|
type TextInputStyleProps = {
|
|
231
227
|
interaction: InteractionType
|
|
232
228
|
success: boolean
|
|
233
229
|
invalid: boolean
|
|
234
|
-
afterElementHasWidth: TextInputState['afterElementHasWidth']
|
|
235
230
|
beforeElementExists: boolean
|
|
236
231
|
}
|
|
237
232
|
|
|
238
|
-
export type {
|
|
239
|
-
TextInputProps,
|
|
240
|
-
TextInputState,
|
|
241
|
-
TextInputStyleProps,
|
|
242
|
-
TextInputStyle
|
|
243
|
-
}
|
|
233
|
+
export type { TextInputProps, TextInputStyleProps, TextInputStyle }
|
|
244
234
|
export { allowedProps }
|