Skip to content

Commit

Permalink
feat(react): added counter to textinput and storybook updates (#12139)
Browse files Browse the repository at this point in the history
* feat(react): added counter to textinput and storybook updates

* chore(react): updated snapshot

* chore(react): updated test input styles and stories

Co-authored-by: TJ Egan <[email protected]>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 29, 2022
1 parent 7e30406 commit 6133193
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 6 deletions.
6 changes: 6 additions & 0 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7835,6 +7835,9 @@ Map {
"disabled": Object {
"type": "bool",
},
"enableCounter": Object {
"type": "bool",
},
"helperText": Object {
"type": "node",
},
Expand All @@ -7861,6 +7864,9 @@ Map {
"light": Object {
"type": "bool",
},
"maxCount": Object {
"type": "number",
},
"onChange": Object {
"type": "func",
},
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/components/TextArea/TextArea-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,13 @@ describe('TextArea', () => {
<TextArea id="counter2" labelText="someLabel" maxCount={5} />
);

it('should not render element without only enableCounter prop passed in', () => {
it('should not render counter with only enableCounter prop passed in', () => {
expect(
counterTestWrapper1.exists(`${prefix}--text-area__counter`)
).toEqual(false);
});

it('should not render element without only maxCount prop passed in', () => {
it('should not render counter with only maxCount prop passed in', () => {
expect(
counterTestWrapper2.exists(`${prefix}--text-area__counter`)
).toEqual(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,11 @@ export const WithLayer = () => {
};

export const Skeleton = () => <TextAreaSkeleton />;

export const Playground = (args) => (
<TextArea
{...args}
labelText="Text area label"
helperText="Optional helper text."
/>
);
46 changes: 43 additions & 3 deletions packages/react/src/components/TextInput/TextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import PropTypes from 'prop-types';
import React, { useContext } from 'react';
import React, { useContext, useState } from 'react';
import classNames from 'classnames';
import { useNormalizedInputProps } from '../../internal/useNormalizedInputProps';
import PasswordInput from './PasswordInput';
Expand Down Expand Up @@ -37,6 +37,8 @@ const TextInput = React.forwardRef(function TextInput(
type = 'text',
warn = false,
warnText,
enableCounter = false,
maxCount,
...rest
},
ref
Expand All @@ -45,6 +47,11 @@ const TextInput = React.forwardRef(function TextInput(

const enabled = useFeatureFlag('enable-v11-release');

const { defaultValue, value } = rest;
const [textCount, setTextCount] = useState(
defaultValue?.length || value?.length || 0
);

const normalizedProps = useNormalizedInputProps({
id,
readOnly,
Expand All @@ -71,6 +78,7 @@ const TextInput = React.forwardRef(function TextInput(
id,
onChange: (evt) => {
if (!normalizedProps.disabled) {
setTextCount(evt.target.value?.length);
onChange(evt);
}
},
Expand All @@ -89,6 +97,11 @@ const TextInput = React.forwardRef(function TextInput(
['aria-describedby']: helperText && normalizedProps.helperId,
...rest,
};

if (enableCounter) {
sharedTextInputProps.maxLength = maxCount;
}

const inputWrapperClasses = classNames(
[
enabled
Expand Down Expand Up @@ -131,12 +144,29 @@ const TextInput = React.forwardRef(function TextInput(
[`${prefix}--text-input__readonly-icon`]: readOnly,
});

const counterClasses = classNames(`${prefix}--label`, {
[`${prefix}--label--disabled`]: disabled,
[`${prefix}--text-input__label-counter`]: true,
});

const counter =
enableCounter && maxCount ? (
<div className={counterClasses}>{`${textCount}/${maxCount}`}</div>
) : null;

const label = labelText ? (
<label htmlFor={id} className={labelClasses}>
{labelText}
</label>
) : null;

const labelWrapper = (
<div className={`${prefix}--text-input__label-wrapper`}>
{label}
{counter}
</div>
);

const helper = helperText ? (
<div id={normalizedProps.helperId} className={helperTextClasses}>
{helperText}
Expand All @@ -160,10 +190,10 @@ const TextInput = React.forwardRef(function TextInput(
return (
<div className={inputWrapperClasses}>
{!inline ? (
label
labelWrapper
) : (
<div className={`${prefix}--text-input__label-helper-wrapper`}>
{label}
{labelWrapper}
{!isFluid && helper}
</div>
)}
Expand Down Expand Up @@ -203,6 +233,11 @@ TextInput.propTypes = {
*/
disabled: PropTypes.bool,

/**
* Specify whether to display the character counter
*/
enableCounter: PropTypes.bool,

/**
* Provide text that is used alongside the control label for additional help
*/
Expand Down Expand Up @@ -245,6 +280,11 @@ TextInput.propTypes = {
*/
light: PropTypes.bool,

/**
* Max character count allowed for the textarea. This is needed in order for enableCounter to display
*/
maxCount: PropTypes.number,

/**
* Optionally provide an `onChange` handler that is called whenever `<input>`
* is updated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,5 +320,40 @@ describe('TextInput', () => {
);
expect(icon).toBeInTheDocument();
});

it('should not render counter with only enableCounter prop passed in', () => {
render(
<TextInput id="input-1" labelText="TextInput label" enableCounter />
);

const counter = screen.queryByText('0/5');

expect(counter).not.toBeInTheDocument();
});

it('should not render counter with only maxCount prop passed in', () => {
render(
<TextInput id="input-1" labelText="TextInput label" enableCounter />
);

const counter = screen.queryByText('0/5');

expect(counter).not.toBeInTheDocument();
});

it('should have the expected classes for counter', () => {
render(
<TextInput
id="input-1"
labelText="TextInput label"
enableCounter
maxCount={5}
/>
);

const counter = screen.queryByText('0/5');

expect(counter).toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ export const Default = () => (

export const Fluid = () => (
<FluidForm>
<TextInput type="text" labelText="Text input label" />
<TextInput type="text" labelText="Text input label" id="text-input-1" />
</FluidForm>
);

export const TogglePasswordVisibility = () => {
return (
<TextInput.PasswordInput
id="text-input-1"
labelText="Text input label"
helperText="Optional help text"
/>
Expand All @@ -50,6 +51,7 @@ export const ReadOnly = () => {
helperText="Optional help text"
value="This is read only, you can't type more."
readOnly
id="text-input-1"
/>
);
};
Expand All @@ -61,18 +63,21 @@ export const WithLayer = () => {
type="text"
labelText="First layer"
helperText="Optional help text"
id="text-input-1"
/>
<Layer>
<TextInput
type="text"
labelText="Second layer"
helperText="Optional help text"
id="text-input-2"
/>
<Layer>
<TextInput
type="text"
labelText="Third layer"
helperText="Optional help text"
id="text-input-3"
/>
</Layer>
</Layer>
Expand All @@ -81,3 +86,94 @@ export const WithLayer = () => {
};

export const Skeleton = () => <TextInputSkeleton />;

export const Playground = (args) => (
<div style={{ width: args.playgroundWidth }}>
<TextInput {...args} id="text-input-1" type="text" />
</div>
);

Playground.argTypes = {
playgroundWidth: {
control: { type: 'range', min: 300, max: 800, step: 50 },
defaultValue: 300,
},
className: {
control: {
type: 'text',
},
defaultValue: 'input-test-class',
},
defaultValue: {
control: {
type: 'text',
},
},
placeholder: {
control: {
type: 'text',
},
defaultValue: 'Placeholder text',
},
invalid: {
control: {
type: 'boolean',
},
defaultValue: false,
},
invalidText: {
control: {
type: 'text',
},
defaultValue: 'Invalid text',
},
disabled: {
control: {
type: 'boolean',
},
defaultValue: false,
},
labelText: {
control: {
type: 'text',
},
defaultValue: 'Label text',
},
helperText: {
control: {
type: 'text',
},
defaultValue: 'Helper text',
},
warn: {
control: {
type: 'boolean',
},
defaultValue: false,
},
warnText: {
control: {
type: 'text',
},
defaultValue:
'Warning message that is really long can wrap to more lines but should not be excessively long.',
},
value: {
control: {
type: 'text',
},
},
onChange: {
action: 'clicked',
},
onClick: {
action: 'clicked',
},
size: {
defaultValue: 'md',
options: ['sm', 'md', 'lg', 'xl'],
control: {
type: 'select',
},
},
};
10 changes: 10 additions & 0 deletions packages/styles/scss/components/text-input/_text-input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -408,4 +408,14 @@
svg {
@include high-contrast-mode('icon-fill');
}

.#{$prefix}--text-input__label-wrapper {
display: flex;
width: 100%;
justify-content: space-between;

.#{$prefix}--text-input__label-counter {
align-self: end;
}
}
}

0 comments on commit 6133193

Please sign in to comment.