@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 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
- ## [11.6.1-snapshot-135](https://github.com/instructure/instructure-ui/compare/v11.6.0...v11.6.1-snapshot-135) (2026-03-18)
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
 
@@ -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, getCSSStyleDeclaration } from '@instructure/ui-dom-utils';
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(prevProps) {
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
- // Spread all sizeVariants except lineHeight (handled above)
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(prevProps) {
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
- // Spread all sizeVariants except lineHeight (handled above)
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.6.1-snapshot-135",
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-axe-check": "11.6.1-snapshot-135",
22
- "@instructure/ui-babel-preset": "11.6.1-snapshot-135",
23
- "@instructure/ui-badge": "11.6.1-snapshot-135",
24
- "@instructure/ui-color-utils": "11.6.1-snapshot-135"
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.6.1-snapshot-135",
29
- "@instructure/shared-types": "11.6.1-snapshot-135",
30
- "@instructure/ui-dom-utils": "11.6.1-snapshot-135",
31
- "@instructure/ui-a11y-utils": "11.6.1-snapshot-135",
32
- "@instructure/ui-form-field": "11.6.1-snapshot-135",
33
- "@instructure/ui-react-utils": "11.6.1-snapshot-135",
34
- "@instructure/ui-tag": "11.6.1-snapshot-135",
35
- "@instructure/ui-themes": "11.6.1-snapshot-135"
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 `IconButton` at the end of a TextInput, e.g. for revealing the content of a password field. In these cases, please use the `withBorder={false}` and `withBackground={false}` props for the IconButton.
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
- <TextInput
178
- renderLabel="Password"
179
- type={showPassword ? 'text' : 'password'}
180
- placeholder="Find something..."
181
- value={passwordValue}
182
- onChange={(e, newValue) => setPasswordValue(newValue)}
183
- renderAfterInput={
184
- <IconButton withBorder={false} withBackground={false} onClick={() => setShowPassword(prevState => !prevState)} screenReaderLabel={showPassword ? 'Hide password' : 'Show password'}>
185
- {showPassword ? <IconOffLine/> : <IconEyeLine/>}
186
- </IconButton>
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={() => (<IconSearchLine inline={false} />)}
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, TextInputState> {
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(prevProps: TextInputProps) {
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 }