Skip to content

Commit

Permalink
feat: add note prop into TextArea and withCounter hoc
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergey Zveroboev committed Aug 14, 2023
1 parent 5067bca commit 75037a5
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.idea
.vscode
.history
.DS_Store
*.log

Expand Down
24 changes: 24 additions & 0 deletions src/components/controls/TextArea/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## TextArea

Additional functionality is enabled via HOCs:

- [withCounter](#withCounter)

### Props

| Property | Type | Default | Description |
| :----------- | :-------------------------------------------------- | :-------------- | :---------------------------------------------------------------------------------------------------------------------------------- |
| autoComplete | `boolean \| string` | `-` | The control's `autocomplete` attribute |
Expand Down Expand Up @@ -28,3 +36,19 @@
| type | `string` | `-` | The control's type |
| value | `string` | `-` | The control's value |
| view | `'normal' \| 'clear'` | `'normal'` | The control's view. `'normal'` by default |
| note | `React.ReactNode` | `-` | An optional element displayed under the lower right corner of the control and sharing the place with the error container |

## withCounter

Adds a special symbol counter under the lower right corner of the control.

### Props

```ts
interface Props {
/**
* Number of max symbols length for counter.
*/
counterMax: number;
}
```
18 changes: 18 additions & 0 deletions src/components/controls/TextArea/TextArea.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,28 @@ $block: '.#{variables.$ns-new}text-area';
}
}

&__additional {
display: flex;
justify-content: space-between;
vertical-align: top;
}

&__note {
margin-left: auto;
}

&__error {
@include mixins.text-body-1();

color: var(--g-color-text-danger);

&:not(:last-child) {
margin-right: var(--g-spacing-2);
}
}

&__note,
&__error {
margin-top: 2px;
}

Expand Down
10 changes: 9 additions & 1 deletion src/components/controls/TextArea/TextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export type TextAreaProps = BaseInputControlProps<HTMLTextAreaElement> & {
minRows?: number;
/** The number of maximum visible text lines for the control. Ignored if `rows` is specified */
maxRows?: number;
/** An optional element displayed under the lower right corner of the control and sharing the place with the error container */
note?: React.ReactNode;
};
export type TextAreaPin = InputControlPin;
export type TextAreaSize = InputControlSize;
Expand Down Expand Up @@ -54,6 +56,7 @@ export const TextArea = React.forwardRef<HTMLSpanElement, TextAreaProps>(functio
className,
qa,
controlProps,
note,
onUpdate,
onChange,
} = props;
Expand Down Expand Up @@ -156,7 +159,12 @@ export const TextArea = React.forwardRef<HTMLSpanElement, TextAreaProps>(functio
/>
)}
</span>
{isErrorMsgVisible && <div className={b('error')}>{error}</div>}
{(isErrorMsgVisible || note) && (
<div className={b('additional')}>
{isErrorMsgVisible && <div className={b('error')}>{error}</div>}
{note && <div className={b('note')}>{note}</div>}
</div>
)}
</span>
);
});
25 changes: 25 additions & 0 deletions src/components/controls/TextArea/__stories__/TextAreaShowcase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import React from 'react';
import block from 'bem-cn-lite';

import {Checkbox} from '../../../Checkbox';
import {Text} from '../../../Text';
import {TextArea} from '../TextArea';
import type {TextAreaProps} from '../TextArea';
import {withCounter} from '../hoc/withCounter';

import './TextAreaShowcase.scss';

const b = block('text-input-showcase');

const TextAreaWithCounter = withCounter(TextArea);

export function TextAreaShowcase() {
const [value, setValue] = React.useState('');
const [isErrorMessageVisible, setErrorMessageVisibility] = React.useState(false);
Expand Down Expand Up @@ -64,6 +68,27 @@ export function TextAreaShowcase() {
controlProps={{style: {resize: 'vertical'}}}
rows={4}
/>
<TextArea
{...textAreaProps}
placeholder="with note"
rows={4}
note={<Text color="secondary">Additional</Text>}
/>
<TextAreaWithCounter
{...textAreaProps}
placeholder="with counter"
counterMax={255}
rows={4}
/>
<TextAreaWithCounter
{...textAreaProps}
placeholder="with counter and long error message"
rows={4}
counterMax={255}
error={
'It happened a very very very very very very very very very very very very very very very very very very very very very long validation error'
}
/>
</div>
</div>
</div>
Expand Down
10 changes: 10 additions & 0 deletions src/components/controls/TextArea/__tests__/TextArea.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ describe('TextArea', () => {
expect(screen.getByText('Some Error')).toBeVisible();
});

test('render note container with note prop', () => {
const {container} = render(
<TextArea error="Some Error" note={<div>Additional</div>} />,
);

// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
expect(container.querySelector('.g-text-area__note')).toBeInTheDocument();
expect(screen.getByText('Additional')).toBeVisible();
});

test('do not show error without error prop', () => {
const {container} = render(<TextArea />);

Expand Down
34 changes: 34 additions & 0 deletions src/components/controls/TextArea/hoc/withCounter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';

import {Text} from '../../../Text';
import {getComponentName} from '../../../utils/getComponentName';
import {TextArea, TextAreaProps} from '../TextArea';

export interface WithCounterProps {
counterMax: number;
}

export function withCounter(
TextAreaComponents: React.ComponentType<TextAreaProps>,
): React.ComponentType<Omit<TextAreaProps, 'note'> & WithCounterProps> {
const componentName = getComponentName(TextAreaComponents);
const displayName = `withCounter(${componentName})`;

return class extends React.Component<Omit<TextAreaProps, 'note'> & WithCounterProps> {
static displayName = displayName;

render() {
return <TextArea {...this.props} note={this.renderNote()} />;
}

private renderNote() {
const {value, counterMax} = this.props;

return (
<Text color="secondary">
{counterMax - (value?.length ?? 0)}/{counterMax}
</Text>
);
}
};
}
3 changes: 3 additions & 0 deletions src/components/controls/TextArea/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export {TextArea} from './TextArea';
export type {TextAreaProps, TextAreaPin, TextAreaSize, TextAreaView} from './TextArea';

export {withCounter} from './hoc/withCounter';
export type {WithCounterProps} from './hoc/withCounter';

0 comments on commit 75037a5

Please sign in to comment.