@instructure/ui-text-input 11.7.4-snapshot-130 → 11.7.4-snapshot-134

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.7.4-snapshot-130](https://github.com/instructure/instructure-ui/compare/v11.7.3...v11.7.4-snapshot-130) (2026-07-23)
6
+ ## [11.7.4-snapshot-134](https://github.com/instructure/instructure-ui/compare/v11.7.3...v11.7.4-snapshot-134) (2026-07-24)
7
7
 
8
8
 
9
9
  ### Bug Fixes
@@ -16,6 +16,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
16
16
  ### Features
17
17
 
18
18
  * **many:** support current spacing tokens in the margin prop for v2 components ([1b47c5f](https://github.com/instructure/instructure-ui/commit/1b47c5f23eaa60b532cdfd53c39bd71f0cf51aaa))
19
+ * **ui-text-input,ui-select:** add contentSpacing prop for even wrapped-tag padding in multiple Select ([0969a00](https://github.com/instructure/instructure-ui/commit/0969a007a16c41eb2b5acec5ba1e4722b8ae0ed4))
19
20
 
20
21
 
21
22
 
@@ -48,6 +48,7 @@ let TextInput = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyl
48
48
  isRequired: false,
49
49
  display: 'block',
50
50
  shouldNotWrap: false,
51
+ renderBeforeInputElementGap: 'default',
51
52
  size: 'medium',
52
53
  textAlign: 'start',
53
54
  messages: []
@@ -22,5 +22,5 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- const allowedProps = ['renderLabel', 'type', 'id', 'value', 'defaultValue', 'interaction', 'messages', 'size', 'textAlign', 'width', 'htmlSize', 'display', 'shouldNotWrap', 'placeholder', 'isRequired', 'elementRef', 'inputRef', 'inputContainerRef', 'renderBeforeInput', 'renderAfterInput', 'onChange', 'onBlur', 'onFocus', 'margin'];
25
+ const allowedProps = ['renderLabel', 'type', 'id', 'value', 'defaultValue', 'interaction', 'messages', 'size', 'textAlign', 'width', 'htmlSize', 'display', 'shouldNotWrap', 'renderBeforeInputElementGap', 'placeholder', 'isRequired', 'elementRef', 'inputRef', 'inputContainerRef', 'renderBeforeInput', 'renderAfterInput', 'onChange', 'onBlur', 'onFocus', 'margin'];
26
26
  export { allowedProps };
@@ -39,7 +39,8 @@ const generateStyle = (componentTheme, props, sharedTokens, state) => {
39
39
  const {
40
40
  size,
41
41
  textAlign,
42
- shouldNotWrap
42
+ shouldNotWrap,
43
+ renderBeforeInputElementGap
43
44
  } = props;
44
45
  const {
45
46
  interaction,
@@ -47,21 +48,40 @@ const generateStyle = (componentTheme, props, sharedTokens, state) => {
47
48
  invalid,
48
49
  beforeElementExists
49
50
  } = state;
51
+ const isEvenSpacing = renderBeforeInputElementGap === 'even';
52
+ const contentGap = {
53
+ small: sharedTokens.spacing.general.spaceXs,
54
+ medium: sharedTokens.spacing.general.spaceXs,
55
+ large: sharedTokens.spacing.general.spaceSm
56
+ }[size];
57
+ const contentPaddingBlock = {
58
+ small: sharedTokens.spacing.general.space2xs,
59
+ medium: sharedTokens.spacing.general.space2xs,
60
+ large: sharedTokens.spacing.general.spaceXs
61
+ }[size];
62
+ const heightTokens = {
63
+ small: componentTheme.heightSm,
64
+ medium: componentTheme.heightMd,
65
+ large: componentTheme.heightLg
66
+ };
67
+ const inputHeightStyle = sizeHeight => isEvenSpacing ? {
68
+ height: 'auto'
69
+ } : {
70
+ height: `calc(${sizeHeight} - (2 * ${componentTheme.borderWidth}))`,
71
+ lineHeight: `calc(${sizeHeight} - (2 * ${componentTheme.borderWidth}))`
72
+ };
50
73
  const sizeVariants = {
51
74
  small: {
52
75
  fontSize: componentTheme.fontSizeSm,
53
- height: `calc(${componentTheme.heightSm} - (2 * ${componentTheme.borderWidth}))`,
54
- lineHeight: `calc(${componentTheme.heightSm} - (2 * ${componentTheme.borderWidth}))`
76
+ ...inputHeightStyle(componentTheme.heightSm)
55
77
  },
56
78
  medium: {
57
79
  fontSize: componentTheme.fontSizeMd,
58
- height: `calc(${componentTheme.heightMd} - (2 * ${componentTheme.borderWidth}))`,
59
- lineHeight: `calc(${componentTheme.heightMd} - (2 * ${componentTheme.borderWidth}))`
80
+ ...inputHeightStyle(componentTheme.heightMd)
60
81
  },
61
82
  large: {
62
83
  fontSize: componentTheme.fontSizeLg,
63
- height: `calc(${componentTheme.heightLg} - (2 * ${componentTheme.borderWidth}))`,
64
- lineHeight: `calc(${componentTheme.heightLg} - (2 * ${componentTheme.borderWidth}))`
84
+ ...inputHeightStyle(componentTheme.heightLg)
65
85
  }
66
86
  };
67
87
  const paddingHorizontalVariants = {
@@ -191,6 +211,11 @@ const generateStyle = (componentTheme, props, sharedTokens, state) => {
191
211
  // left padding of the `renderBeforeInput` element
192
212
  ...(beforeElementExists && {
193
213
  paddingInlineStart: paddingHorizontalVariants[size]
214
+ }),
215
+ ...(isEvenSpacing && {
216
+ minHeight: heightTokens[size],
217
+ paddingBlock: contentPaddingBlock,
218
+ gap: contentGap
194
219
  })
195
220
  },
196
221
  inputLayout: {
@@ -203,12 +228,17 @@ const generateStyle = (componentTheme, props, sharedTokens, state) => {
203
228
  flexDirection: 'row'
204
229
  },
205
230
  beforeElement: {
206
- ...(interaction === 'disabled' ? {
231
+ label: 'textInput__beforeElement',
232
+ ...(interaction === 'disabled' && !isEvenSpacing ? {
207
233
  opacity: 0.5
208
234
  } : {
209
235
  display: 'contents'
210
236
  }),
211
- label: 'textInput__beforeElement'
237
+ ...(interaction === 'disabled' && isEvenSpacing && {
238
+ '& > *': {
239
+ opacity: 0.5
240
+ }
241
+ })
212
242
  },
213
243
  afterElement: {
214
244
  ...(interaction === 'disabled' && {
@@ -59,6 +59,7 @@ let TextInput = exports.TextInput = (_dec = (0, _withDeterministicId.withDetermi
59
59
  isRequired: false,
60
60
  display: 'block',
61
61
  shouldNotWrap: false,
62
+ renderBeforeInputElementGap: 'default',
62
63
  size: 'medium',
63
64
  textAlign: 'start',
64
65
  messages: []
@@ -28,4 +28,4 @@ exports.allowedProps = void 0;
28
28
  * SOFTWARE.
29
29
  */
30
30
 
31
- const allowedProps = exports.allowedProps = ['renderLabel', 'type', 'id', 'value', 'defaultValue', 'interaction', 'messages', 'size', 'textAlign', 'width', 'htmlSize', 'display', 'shouldNotWrap', 'placeholder', 'isRequired', 'elementRef', 'inputRef', 'inputContainerRef', 'renderBeforeInput', 'renderAfterInput', 'onChange', 'onBlur', 'onFocus', 'margin'];
31
+ const allowedProps = exports.allowedProps = ['renderLabel', 'type', 'id', 'value', 'defaultValue', 'interaction', 'messages', 'size', 'textAlign', 'width', 'htmlSize', 'display', 'shouldNotWrap', 'renderBeforeInputElementGap', 'placeholder', 'isRequired', 'elementRef', 'inputRef', 'inputContainerRef', 'renderBeforeInput', 'renderAfterInput', 'onChange', 'onBlur', 'onFocus', 'margin'];
@@ -44,7 +44,8 @@ const generateStyle = (componentTheme, props, sharedTokens, state) => {
44
44
  const {
45
45
  size,
46
46
  textAlign,
47
- shouldNotWrap
47
+ shouldNotWrap,
48
+ renderBeforeInputElementGap
48
49
  } = props;
49
50
  const {
50
51
  interaction,
@@ -52,21 +53,40 @@ const generateStyle = (componentTheme, props, sharedTokens, state) => {
52
53
  invalid,
53
54
  beforeElementExists
54
55
  } = state;
56
+ const isEvenSpacing = renderBeforeInputElementGap === 'even';
57
+ const contentGap = {
58
+ small: sharedTokens.spacing.general.spaceXs,
59
+ medium: sharedTokens.spacing.general.spaceXs,
60
+ large: sharedTokens.spacing.general.spaceSm
61
+ }[size];
62
+ const contentPaddingBlock = {
63
+ small: sharedTokens.spacing.general.space2xs,
64
+ medium: sharedTokens.spacing.general.space2xs,
65
+ large: sharedTokens.spacing.general.spaceXs
66
+ }[size];
67
+ const heightTokens = {
68
+ small: componentTheme.heightSm,
69
+ medium: componentTheme.heightMd,
70
+ large: componentTheme.heightLg
71
+ };
72
+ const inputHeightStyle = sizeHeight => isEvenSpacing ? {
73
+ height: 'auto'
74
+ } : {
75
+ height: `calc(${sizeHeight} - (2 * ${componentTheme.borderWidth}))`,
76
+ lineHeight: `calc(${sizeHeight} - (2 * ${componentTheme.borderWidth}))`
77
+ };
55
78
  const sizeVariants = {
56
79
  small: {
57
80
  fontSize: componentTheme.fontSizeSm,
58
- height: `calc(${componentTheme.heightSm} - (2 * ${componentTheme.borderWidth}))`,
59
- lineHeight: `calc(${componentTheme.heightSm} - (2 * ${componentTheme.borderWidth}))`
81
+ ...inputHeightStyle(componentTheme.heightSm)
60
82
  },
61
83
  medium: {
62
84
  fontSize: componentTheme.fontSizeMd,
63
- height: `calc(${componentTheme.heightMd} - (2 * ${componentTheme.borderWidth}))`,
64
- lineHeight: `calc(${componentTheme.heightMd} - (2 * ${componentTheme.borderWidth}))`
85
+ ...inputHeightStyle(componentTheme.heightMd)
65
86
  },
66
87
  large: {
67
88
  fontSize: componentTheme.fontSizeLg,
68
- height: `calc(${componentTheme.heightLg} - (2 * ${componentTheme.borderWidth}))`,
69
- lineHeight: `calc(${componentTheme.heightLg} - (2 * ${componentTheme.borderWidth}))`
89
+ ...inputHeightStyle(componentTheme.heightLg)
70
90
  }
71
91
  };
72
92
  const paddingHorizontalVariants = {
@@ -196,6 +216,11 @@ const generateStyle = (componentTheme, props, sharedTokens, state) => {
196
216
  // left padding of the `renderBeforeInput` element
197
217
  ...(beforeElementExists && {
198
218
  paddingInlineStart: paddingHorizontalVariants[size]
219
+ }),
220
+ ...(isEvenSpacing && {
221
+ minHeight: heightTokens[size],
222
+ paddingBlock: contentPaddingBlock,
223
+ gap: contentGap
199
224
  })
200
225
  },
201
226
  inputLayout: {
@@ -208,12 +233,17 @@ const generateStyle = (componentTheme, props, sharedTokens, state) => {
208
233
  flexDirection: 'row'
209
234
  },
210
235
  beforeElement: {
211
- ...(interaction === 'disabled' ? {
236
+ label: 'textInput__beforeElement',
237
+ ...(interaction === 'disabled' && !isEvenSpacing ? {
212
238
  opacity: 0.5
213
239
  } : {
214
240
  display: 'contents'
215
241
  }),
216
- label: 'textInput__beforeElement'
242
+ ...(interaction === 'disabled' && isEvenSpacing && {
243
+ '& > *': {
244
+ opacity: 0.5
245
+ }
246
+ })
217
247
  },
218
248
  afterElement: {
219
249
  ...(interaction === 'disabled' && {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-text-input",
3
- "version": "11.7.4-snapshot-130",
3
+ "version": "11.7.4-snapshot-134",
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": "16.3.2",
19
19
  "@testing-library/user-event": "^14.6.1",
20
20
  "vitest": "^4.1.9",
21
- "@instructure/ui-axe-check": "11.7.4-snapshot-130",
22
- "@instructure/ui-badge": "11.7.4-snapshot-130",
23
- "@instructure/ui-babel-preset": "11.7.4-snapshot-130",
24
- "@instructure/ui-color-utils": "11.7.4-snapshot-130"
21
+ "@instructure/ui-axe-check": "11.7.4-snapshot-134",
22
+ "@instructure/ui-babel-preset": "11.7.4-snapshot-134",
23
+ "@instructure/ui-badge": "11.7.4-snapshot-134",
24
+ "@instructure/ui-color-utils": "11.7.4-snapshot-134"
25
25
  },
26
26
  "dependencies": {
27
27
  "@babel/runtime": "^7.29.7",
28
- "@instructure/emotion": "11.7.4-snapshot-130",
29
- "@instructure/ui-a11y-utils": "11.7.4-snapshot-130",
30
- "@instructure/ui-dom-utils": "11.7.4-snapshot-130",
31
- "@instructure/ui-form-field": "11.7.4-snapshot-130",
32
- "@instructure/shared-types": "11.7.4-snapshot-130",
33
- "@instructure/ui-react-utils": "11.7.4-snapshot-130",
34
- "@instructure/ui-tag": "11.7.4-snapshot-130",
35
- "@instructure/ui-themes": "11.7.4-snapshot-130"
28
+ "@instructure/emotion": "11.7.4-snapshot-134",
29
+ "@instructure/shared-types": "11.7.4-snapshot-134",
30
+ "@instructure/ui-dom-utils": "11.7.4-snapshot-134",
31
+ "@instructure/ui-form-field": "11.7.4-snapshot-134",
32
+ "@instructure/ui-react-utils": "11.7.4-snapshot-134",
33
+ "@instructure/ui-tag": "11.7.4-snapshot-134",
34
+ "@instructure/ui-a11y-utils": "11.7.4-snapshot-134",
35
+ "@instructure/ui-themes": "11.7.4-snapshot-134"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "react": ">=18 <=19"
@@ -97,7 +97,10 @@ type: example
97
97
 
98
98
  TextInput accepts focusable and non-focusable content before and/or after
99
99
  the input text. A common use case is adding an icon or avatar to the input.
100
- Focusable content will be focused separately from the input itself.
100
+ Focusable content will be focused separately from the input itself. When
101
+ rendering multiple elements before the input (such as tags), set
102
+ `renderBeforeInputElementGap="even"` to evenly space them, including as they
103
+ wrap to multiple rows.
101
104
 
102
105
  ```js
103
106
  ---
@@ -121,33 +124,29 @@ type: example
121
124
  renderLabel="What are Paula Panda's favorite ice cream flavors?"
122
125
  value={this.state.value}
123
126
  onChange={this.handleChange}
127
+ renderBeforeInputElementGap="even"
124
128
  renderBeforeInput={
125
129
  <>
126
130
  {this.state.value !== '' && (
127
131
  <Tag
128
132
  text={this.state.value}
129
- margin="general.space2xs general.spaceXs general.space2xs none"
130
133
  onClick={() => console.log(this.state.value)}
131
134
  />
132
135
  )}
133
136
  <Tag
134
137
  text="Rocky road"
135
- margin="general.space2xs general.spaceXs general.space2xs none"
136
138
  onClick={() => console.log('Rocky road')}
137
139
  />
138
140
  <Tag
139
141
  text="Vanilla"
140
- margin="general.space2xs general.spaceXs general.space2xs none"
141
142
  onClick={() => console.log('Vanilla')}
142
143
  />
143
144
  <Tag
144
145
  text="Coffee"
145
- margin="general.space2xs general.spaceXs general.space2xs none"
146
146
  onClick={() => console.log('Coffee')}
147
147
  />
148
148
  <Tag
149
149
  text="Strawberry"
150
- margin="general.space2xs general.spaceXs general.space2xs none"
151
150
  onClick={() => console.log('Strawberry')}
152
151
  />
153
152
  </>
@@ -266,7 +265,7 @@ You can see here most of the visual states of the component:
266
265
  ---
267
266
  type: example
268
267
  ---
269
- <Flex gap='medium' direction='column'>
268
+ <Flex gap='general.spaceXl' direction='column'>
270
269
  <TextInput
271
270
  size="small"
272
271
  renderAfterInput={<AlarmClockInstUIIcon />}
@@ -61,6 +61,7 @@ class TextInput extends Component<TextInputProps> {
61
61
  isRequired: false,
62
62
  display: 'block',
63
63
  shouldNotWrap: false,
64
+ renderBeforeInputElementGap: 'default',
64
65
  size: 'medium',
65
66
  textAlign: 'start',
66
67
  messages: []
@@ -121,6 +121,14 @@ type TextInputOwnProps = {
121
121
  */
122
122
  shouldNotWrap?: boolean
123
123
 
124
+ /**
125
+ * Whether to apply a gap between the elements rendered via `renderBeforeInput`
126
+ * (a CSS flex container), e.g. tags.
127
+ * - `'default'`: no gap is applied between the elements.
128
+ * - `'even'`: wrapped rows are evenly spaced with a vertical and horizontal gap.
129
+ */
130
+ renderBeforeInputElementGap?: 'default' | 'even'
131
+
124
132
  /**
125
133
  * Html placeholder text to display when the input has no value. This should be hint text, not a label replacement.
126
134
  */
@@ -218,6 +226,7 @@ const allowedProps: AllowedPropKeys = [
218
226
  'htmlSize',
219
227
  'display',
220
228
  'shouldNotWrap',
229
+ 'renderBeforeInputElementGap',
221
230
  'placeholder',
222
231
  'isRequired',
223
232
  'elementRef',
@@ -43,24 +43,49 @@ const generateStyle = (
43
43
  sharedTokens: SharedTokens,
44
44
  state: TextInputStyleProps
45
45
  ): TextInputStyle => {
46
- const { size, textAlign, shouldNotWrap } = props
46
+ const { size, textAlign, shouldNotWrap, renderBeforeInputElementGap } = props
47
47
  const { interaction, success, invalid, beforeElementExists } = state
48
48
 
49
+ const isEvenSpacing = renderBeforeInputElementGap === 'even'
50
+
51
+ const contentGap = {
52
+ small: sharedTokens.spacing.general.spaceXs,
53
+ medium: sharedTokens.spacing.general.spaceXs,
54
+ large: sharedTokens.spacing.general.spaceSm
55
+ }[size!]
56
+
57
+ const contentPaddingBlock = {
58
+ small: sharedTokens.spacing.general.space2xs,
59
+ medium: sharedTokens.spacing.general.space2xs,
60
+ large: sharedTokens.spacing.general.spaceXs
61
+ }[size!]
62
+
63
+ const heightTokens = {
64
+ small: componentTheme.heightSm,
65
+ medium: componentTheme.heightMd,
66
+ large: componentTheme.heightLg
67
+ }
68
+
69
+ const inputHeightStyle = (sizeHeight: string) =>
70
+ isEvenSpacing
71
+ ? { height: 'auto' }
72
+ : {
73
+ height: `calc(${sizeHeight} - (2 * ${componentTheme.borderWidth}))`,
74
+ lineHeight: `calc(${sizeHeight} - (2 * ${componentTheme.borderWidth}))`
75
+ }
76
+
49
77
  const sizeVariants = {
50
78
  small: {
51
79
  fontSize: componentTheme.fontSizeSm,
52
- height: `calc(${componentTheme.heightSm} - (2 * ${componentTheme.borderWidth}))`,
53
- lineHeight: `calc(${componentTheme.heightSm} - (2 * ${componentTheme.borderWidth}))`
80
+ ...inputHeightStyle(componentTheme.heightSm)
54
81
  },
55
82
  medium: {
56
83
  fontSize: componentTheme.fontSizeMd,
57
- height: `calc(${componentTheme.heightMd} - (2 * ${componentTheme.borderWidth}))`,
58
- lineHeight: `calc(${componentTheme.heightMd} - (2 * ${componentTheme.borderWidth}))`
84
+ ...inputHeightStyle(componentTheme.heightMd)
59
85
  },
60
86
  large: {
61
87
  fontSize: componentTheme.fontSizeLg,
62
- height: `calc(${componentTheme.heightLg} - (2 * ${componentTheme.borderWidth}))`,
63
- lineHeight: `calc(${componentTheme.heightLg} - (2 * ${componentTheme.borderWidth}))`
88
+ ...inputHeightStyle(componentTheme.heightLg)
64
89
  }
65
90
  }
66
91
  const paddingHorizontalVariants = {
@@ -194,6 +219,11 @@ const generateStyle = (
194
219
  // left padding of the `renderBeforeInput` element
195
220
  ...(beforeElementExists && {
196
221
  paddingInlineStart: paddingHorizontalVariants[size!]
222
+ }),
223
+ ...(isEvenSpacing && {
224
+ minHeight: heightTokens[size!],
225
+ paddingBlock: contentPaddingBlock,
226
+ gap: contentGap
197
227
  })
198
228
  },
199
229
  inputLayout: {
@@ -206,10 +236,12 @@ const generateStyle = (
206
236
  flexDirection: 'row'
207
237
  },
208
238
  beforeElement: {
209
- ...(interaction === 'disabled'
239
+ label: 'textInput__beforeElement',
240
+ ...(interaction === 'disabled' && !isEvenSpacing
210
241
  ? { opacity: 0.5 }
211
242
  : { display: 'contents' }),
212
- label: 'textInput__beforeElement'
243
+ ...(interaction === 'disabled' &&
244
+ isEvenSpacing && { '& > *': { opacity: 0.5 } })
213
245
  },
214
246
  afterElement: {
215
247
  ...(interaction === 'disabled' && { opacity: 0.5 }),