Skip to content

Commit 8959b3a

Browse files
authored
feat(text-input): new xs size (#22038)
* feat(text-input): new xs size * fix: password input, flex wrap * fix(form): break word on overflow * feat(storybook): new size control and inline story * chore: update snapshots * feat(skeleton): size
1 parent 5e89339 commit 8959b3a

13 files changed

Lines changed: 213 additions & 83 deletions

File tree

packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10721,6 +10721,7 @@ Map {
1072110721
"size": {
1072210722
"args": [
1072310723
[
10724+
"xs",
1072410725
"sm",
1072510726
"md",
1072610727
"lg",
@@ -10762,6 +10763,17 @@ Map {
1076210763
"hideLabel": {
1076310764
"type": "bool",
1076410765
},
10766+
"size": {
10767+
"args": [
10768+
[
10769+
"xs",
10770+
"sm",
10771+
"md",
10772+
"lg",
10773+
],
10774+
],
10775+
"type": "oneOf",
10776+
},
1076510777
},
1076610778
},
1076710779
"Theme" => {

packages/react/src/components/TextInput/PasswordInput.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,14 +263,16 @@ const PasswordInput = forwardRef<unknown, PasswordInputProps>(
263263
[`${prefix}--text-input-wrapper--readonly`]: readOnly,
264264
[`${prefix}--text-input-wrapper--light`]: light,
265265
[`${prefix}--text-input-wrapper--inline`]: inline,
266+
[`${prefix}--text-input-wrapper--inline--invalid`]:
267+
inline && normalizedProps.invalid,
266268
[`${prefix}--text-input--fluid`]: isFluid,
267269
}
268270
);
269271
const labelClasses = classNames(`${prefix}--label`, {
270272
[`${prefix}--visually-hidden`]: hideLabel,
271273
[`${prefix}--label--disabled`]: disabled,
272274
[`${prefix}--label--inline`]: inline,
273-
[`${prefix}--label--inline--${size}`]: inline && !!size,
275+
[`${prefix}--label--inline--${size}`]: inline && !!size, // TODO v12 - remove this class
274276
});
275277
const helperTextClasses = classNames(`${prefix}--form__helper-text`, {
276278
[`${prefix}--form__helper-text--disabled`]: disabled,
@@ -402,7 +404,6 @@ const PasswordInput = forwardRef<unknown, PasswordInputProps>(
402404
) : (
403405
<div className={`${prefix}--text-input__label-helper-wrapper`}>
404406
{label}
405-
{!isFluid && helper}
406407
</div>
407408
)}
408409
<div className={fieldOuterWrapperClasses}>
@@ -415,6 +416,11 @@ const PasswordInput = forwardRef<unknown, PasswordInputProps>(
415416
</div>
416417
{!isFluid && !inline && (normalizedProps.validation || helper)}
417418
</div>
419+
{inline && !isFluid && (
420+
<div className={`${prefix}--text-input__label-helper-wrapper`}>
421+
{normalizedProps.validation || helper}
422+
</div>
423+
)}
418424
</div>
419425
);
420426
}

packages/react/src/components/TextInput/TextInput.Skeleton.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2023
2+
* Copyright IBM Corp. 2016, 2026
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
@@ -21,16 +21,26 @@ export interface TextInputSkeletonProps
2121
* Specify whether the label should be hidden or not.
2222
*/
2323
hideLabel?: boolean;
24+
25+
/**
26+
* Specify the size of the TextInputSkeleton
27+
*/
28+
size?: 'xs' | 'sm' | 'md' | 'lg';
2429
}
2530

2631
const TextInputSkeleton = ({
2732
hideLabel,
2833
className,
34+
size,
2935
...rest
3036
}: TextInputSkeletonProps) => {
3137
const prefix = usePrefix();
3238
return (
33-
<div className={cx(`${prefix}--form-item`, className)} {...rest}>
39+
<div
40+
className={cx(`${prefix}--form-item`, className, {
41+
[`${prefix}--layout--size-${size}`]: size,
42+
})}
43+
{...rest}>
3444
{!hideLabel && (
3545
<span className={`${prefix}--label ${prefix}--skeleton`} />
3646
)}
@@ -49,6 +59,11 @@ TextInputSkeleton.propTypes = {
4959
* Specify whether the label should be hidden, or not
5060
*/
5161
hideLabel: PropTypes.bool,
62+
63+
/**
64+
* Specify the size of the TextInputSkeleton
65+
*/
66+
size: PropTypes.oneOf(['xs', 'sm', 'md', 'lg']),
5267
};
5368

5469
export default TextInputSkeleton;

packages/react/src/components/TextInput/TextInput.stories.js

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2025
2+
* Copyright IBM Corp. 2016, 2026
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
@@ -112,7 +112,7 @@ export default {
112112
action: 'onClick',
113113
},
114114
size: {
115-
options: ['sm', 'md', 'lg'],
115+
options: ['xs', 'sm', 'md', 'lg'],
116116
control: {
117117
type: 'select',
118118
},
@@ -176,6 +176,27 @@ export const Default = (args) => {
176176
);
177177
};
178178

179+
export const Inline = (args) => {
180+
const { defaultWidth, ...textInputArgs } = args;
181+
182+
return (
183+
<div style={{ width: defaultWidth }}>
184+
<TextInput inline {...textInputArgs} />
185+
</div>
186+
);
187+
};
188+
189+
Inline.args = {
190+
defaultWidth: 450,
191+
inline: true,
192+
};
193+
194+
Inline.parameters = {
195+
controls: {
196+
exclude: ['inline'],
197+
},
198+
};
199+
179200
export const Fluid = (args) => {
180201
const { defaultWidth, ...textInputArgs } = args;
181202

@@ -285,15 +306,13 @@ export const withAILabel = (args) => {
285306
);
286307
};
287308

288-
export const Skeleton = (args) => <TextInputSkeleton {...args} />;
289-
290-
Skeleton.args = {
291-
hideLabel: false,
292-
};
309+
export const Skeleton = ({ hideLabel, size }) => (
310+
<TextInputSkeleton hideLabel={hideLabel} size={size} />
311+
);
293312

294313
Skeleton.parameters = {
295314
controls: {
296-
include: ['hideLabel'],
315+
include: ['hideLabel', 'size'],
297316
},
298317
};
299318

packages/react/src/components/TextInput/TextInput.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export interface TextInputProps
133133
/**
134134
* Specify the size of the Text Input. Currently supports the following:
135135
*/
136-
size?: 'sm' | 'md' | 'lg' | 'xl';
136+
size?: 'xs' | 'sm' | 'md' | 'lg';
137137

138138
/**
139139
* @deprecated please use `decorator` instead.
@@ -271,7 +271,7 @@ const TextInput = forwardRef<unknown, TextInputProps>(
271271
[`${prefix}--visually-hidden`]: hideLabel,
272272
[`${prefix}--label--disabled`]: normalizedProps.disabled,
273273
[`${prefix}--label--inline`]: inline,
274-
[`${prefix}--label--inline--${size}`]: inline && !!size,
274+
[`${prefix}--label--inline--${size}`]: inline && !!size, // TODO v12 - remove this class
275275
});
276276
const helperTextClasses = classNames(`${prefix}--form__helper-text`, {
277277
[`${prefix}--form__helper-text--disabled`]: normalizedProps.disabled,
@@ -386,15 +386,14 @@ const TextInput = forwardRef<unknown, TextInputProps>(
386386
) : (
387387
<div className={`${prefix}--text-input__label-helper-wrapper`}>
388388
{labelWrapper}
389-
{!isFluid && (normalizedProps.validation || helper)}
390389
</div>
391390
)}
392391
<div className={fieldOuterWrapperClasses}>
393392
<div
394393
className={fieldWrapperClasses}
395394
data-invalid={normalizedProps.invalid || null}>
396-
{Icon && <Icon className={iconClasses} />}
397395
{input}
396+
{Icon && <Icon className={iconClasses} />}
398397
{slug ? (
399398
normalizedDecorator
400399
) : decorator ? (
@@ -418,6 +417,11 @@ const TextInput = forwardRef<unknown, TextInputProps>(
418417
</div>
419418
{!isFluid && !inline && (normalizedProps.validation || helper)}
420419
</div>
420+
{inline && !isFluid && (
421+
<div className={`${prefix}--text-input__label-helper-wrapper`}>
422+
{normalizedProps.validation || helper}
423+
</div>
424+
)}
421425
</div>
422426
);
423427
}
@@ -526,7 +530,7 @@ TextInput.propTypes = {
526530
/**
527531
* Specify the size of the Text Input. Currently supports the following:
528532
*/
529-
size: PropTypes.oneOf(['sm', 'md', 'lg']),
533+
size: PropTypes.oneOf(['xs', 'sm', 'md', 'lg']),
530534

531535
/**
532536
* **Experimental**: Provide a `Slug` component to be rendered inside the `TextInput` component

packages/styles/scss/components/form/_form.scss

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright IBM Corp. 2016, 2023
2+
// Copyright IBM Corp. 2016, 2026
33
//
44
// This source code is licensed under the Apache-2.0 license found in the
55
// LICENSE file in the root directory of this source tree.
@@ -223,6 +223,8 @@ $input-label-weight: 400 !default;
223223
overflow: hidden;
224224
margin: $spacing-02 0 0;
225225
max-block-size: 0;
226+
overflow-wrap: break-word;
227+
word-break: break-word;
226228
}
227229

228230
.#{$prefix}--select--inline .#{$prefix}--form__helper-text {

packages/styles/scss/components/text-input/_text-input.scss

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright IBM Corp. 2016, 2025
2+
// Copyright IBM Corp. 2016, 2026
33
//
44
// This source code is licensed under the Apache-2.0 license found in the
55
// LICENSE file in the root directory of this source tree.
@@ -31,7 +31,7 @@
3131
/// @group text-input
3232
@mixin text-input {
3333
.#{$prefix}--text-input {
34-
@include layout.use('size', $default: 'md', $min: 'sm', $max: 'lg');
34+
@include layout.use('size', $default: 'md', $min: 'xs', $max: 'lg');
3535
@include layout.use('density', $default: 'normal');
3636
@include component-reset.reset;
3737
@include type-style('body-compact-01');
@@ -363,36 +363,43 @@
363363
//-----------------------------
364364

365365
.#{$prefix}--text-input-wrapper.#{$prefix}--text-input-wrapper--inline {
366-
flex-flow: row wrap;
366+
flex-flow: row nowrap;
367+
align-items: center;
367368
}
368369

369370
.#{$prefix}--text-input-wrapper .#{$prefix}--label--inline {
370-
flex: 1;
371-
margin: convert.to-rem(13px) 0 0 0;
371+
flex: 0 1 auto;
372+
margin: 0;
372373
overflow-wrap: break-word;
373374
word-break: break-word;
374375
}
375376

376-
.#{$prefix}--text-input-wrapper .#{$prefix}--label--inline--sm {
377-
margin-block-start: convert.to-rem(9px);
378-
}
379-
380-
.#{$prefix}--text-input-wrapper .#{$prefix}--label--inline--lg {
381-
margin-block-start: convert.to-rem(17px);
382-
}
383-
384377
.#{$prefix}--text-input__label-helper-wrapper {
385-
flex: 2;
378+
flex: 0 1 auto;
386379
flex-direction: column;
387-
margin-inline-end: convert.to-rem(24px);
388-
max-inline-size: convert.to-rem(128px);
380+
max-inline-size: convert.to-rem(142px);
389381
overflow-wrap: break-word;
382+
383+
&:first-of-type {
384+
margin-inline-end: $spacing-03;
385+
}
386+
387+
&:last-of-type {
388+
margin-inline-start: $spacing-03;
389+
}
390390
}
391391

392392
.#{$prefix}--text-input-wrapper .#{$prefix}--form__helper-text--inline {
393393
margin-block-start: convert.to-rem(2px);
394394
}
395395

396+
.#{$prefix}--text-input-wrapper--inline {
397+
.#{$prefix}--form__helper-text--inline,
398+
.#{$prefix}--form-requirement {
399+
margin: 0;
400+
}
401+
}
402+
396403
.#{$prefix}--text-input__field-outer-wrapper {
397404
display: flex;
398405
flex: 1 1 auto;
@@ -460,15 +467,18 @@
460467
) {
461468
@include ai-gradient;
462469
}
463-
464470
.#{$prefix}--text-input__field-wrapper--decorator
465-
.#{$prefix}--text-input:has(
466-
~ .#{$prefix}--text-input__field-inner-wrapper--decorator > *
467-
),
471+
.#{$prefix}--text-input:not(.#{$prefix}--text-input--invalid):not(
472+
.#{$prefix}--text-input--warning
473+
):has(~ .#{$prefix}--text-input__field-inner-wrapper--decorator > *),
468474
.#{$prefix}--text-input__field-wrapper--slug
469-
.#{$prefix}--text-input:has(~ .#{$prefix}--ai-label),
475+
.#{$prefix}--text-input:not(.#{$prefix}--text-input--invalid):not(
476+
.#{$prefix}--text-input--warning
477+
):has(~ .#{$prefix}--ai-label),
470478
.#{$prefix}--text-input__field-wrapper--slug
471-
.#{$prefix}--text-input:has(~ .#{$prefix}--slug) {
479+
.#{$prefix}--text-input:not(.#{$prefix}--text-input--invalid):not(
480+
.#{$prefix}--text-input--warning
481+
):has(~ .#{$prefix}--slug) {
472482
padding-inline-end: $spacing-08;
473483
}
474484

@@ -485,17 +495,25 @@
485495
padding-inline-end: $spacing-10;
486496
}
487497

488-
.#{$prefix}--text-input--invalid
489-
~ .#{$prefix}--text-input__field-inner-wrapper--decorator
490-
> *,
491-
.#{$prefix}--text-input--warning
492-
~ .#{$prefix}--text-input__field-inner-wrapper--decorator
493-
> *,
494-
.#{$prefix}--text-input--invalid ~ .#{$prefix}--ai-label,
495-
.#{$prefix}--text-input--warning ~ .#{$prefix}--ai-label,
496-
.#{$prefix}--text-input--invalid ~ .#{$prefix}--slug,
497-
.#{$prefix}--text-input--warning ~ .#{$prefix}--slug {
498-
inset-inline-end: $spacing-08;
498+
.#{$prefix}--text-input__field-wrapper--decorator,
499+
.#{$prefix}--text-input__field-wrapper--slug {
500+
.#{$prefix}--text-input--invalid ~ .#{$prefix}--text-input__invalid-icon,
501+
.#{$prefix}--text-input--warning ~ .#{$prefix}--text-input__invalid-icon {
502+
inset-inline-end: $spacing-08;
503+
}
504+
505+
.#{$prefix}--text-input--invalid
506+
~ .#{$prefix}--text-input__field-inner-wrapper--decorator
507+
> *,
508+
.#{$prefix}--text-input--warning
509+
~ .#{$prefix}--text-input__field-inner-wrapper--decorator
510+
> *,
511+
.#{$prefix}--text-input--invalid ~ .#{$prefix}--ai-label,
512+
.#{$prefix}--text-input--warning ~ .#{$prefix}--ai-label,
513+
.#{$prefix}--text-input--invalid ~ .#{$prefix}--slug,
514+
.#{$prefix}--text-input--warning ~ .#{$prefix}--slug {
515+
inset-inline-end: $spacing-05;
516+
}
499517
}
500518

501519
// Decorator

0 commit comments

Comments
 (0)