Skip to content

Commit

Permalink
add TextArea
Browse files Browse the repository at this point in the history
  • Loading branch information
Barry127 committed Nov 12, 2020
1 parent 82cf29e commit 67140c7
Show file tree
Hide file tree
Showing 14 changed files with 472 additions and 3 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"prepub",
"renderprops",
"rerender",
"scrollbars",
"secondaryfill",
"shorthash",
"tabindex",
Expand Down
6 changes: 6 additions & 0 deletions docsComponents/side-nav.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
}

.content {
@include scrollbars();
position: sticky;
overflow-y: auto;

Expand Down Expand Up @@ -104,5 +105,10 @@
right: 0;
max-width: none;
z-index: 10;

& + * {
height: 0;
overflow: hidden;
}
}
}
101 changes: 101 additions & 0 deletions pages/docs/components/Form/TextArea.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { Description, DoDont, Meta, Props } from 'docsComponents';

<Meta title="TextArea" />

# TextArea

<Description of="TextArea" />

## Props

<Props of="TextArea" />

## Examples

### Basic

A basic TextArea.

```js live
<TextArea placeholder="Type here...">
Children are placed under the textarea
</TextArea>
```

### Autosize

Textarea autosize can be on or off.

```js live
<Row gutter={1}>
<Col>
<TextArea placeholder="I will grow in height as my input grows" />
</Col>
<Col>
<TextArea autoSize={false} placeholder="I will not grow in height" />
</Col>
</Row>
```

### Disabled

TextArea can be disabled.

```js live
<Row gutter={1}>
<Col>
<TextArea disabled value="Disabled" />
</Col>
<Col>
<TextArea readOnly value="Read Only" />
</Col>
</Row>
```

### Size

TextArea can have different sizes.

```js live
<Row gutter={1}>
<Col span={8}>
<TextArea size="sm" placeholder="sm" />
</Col>
<Col span={8}>
<TextArea size="md" placeholder="md" />
</Col>
<Col span={8}>
<TextArea size="lg" placeholder="lg" />
</Col>
</Row>
```

### Error

A textarea can indicate it has an error.

```js live
<Row gutter={1}>
<Col span={12}>
<TextArea hasError placeholder="Error..." />
</Col>
<Col span={12}>
<TextArea hasError placeholder="Error...">
Text indicating error
</TextArea>
</Col>
</Row>
```

## Design Guidelines

<DoDont
do={[
'Use TextArea to get multiline data input from a user.',
'Make use of a label when possible otherwise set aria-label.'
]}
dont={[
'Use TextArea in an inaccessible way for screen readers.',
'Use TextArea when only a single line of data is required.'
]}
/>
1 change: 1 addition & 0 deletions pages/docs/nav.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"Form": "/docs/components/Form/Form",
"FormField": "/docs/components/Form/FormField",
"PasswordInput": "/docs/components/Form/PasswordInput",
"TextArea": "/docs/components/Form/TextArea",
"TextInput": "/docs/components/Form/TextInput",
"TypeAheadInput": "/docs/components/Form/TypeAheadInput"
}
Expand Down
40 changes: 40 additions & 0 deletions src/common/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,43 @@ $elevation4-dark: 0 1px 1px $elevation-color-dark,
}
}
}

@mixin scrollbars(
$background: var(--mvn-color-background),
$background-dark: var(--mvn-color-background-dark),
$width: 1.2rem
) {
&::-webkit-scrollbar {
background: transparent;
position: absolute;
width: $width;
}

&::-webkit-scrollbar-button {
display: none;
}

&::-webkit-scrollbar-track {
background: transparent;
}

&::-webkit-scrollbar-thumb {
background: var(--mvn-color-scrollbar-a20);
border: 1px solid $background;
border-radius: calc(#{$width} / 2);
min-height: calc(#{$width} * 2);

&:hover {
background: var(--mvn-color-scrollbar-a50);
}

:global(.mvn-dark) & {
background: var(--mvn-color-scrollbar-dark-a20);
border: 1px solid $background-dark;

&:hover {
background: var(--mvn-color-scrollbar-dark-a50);
}
}
}
}
105 changes: 105 additions & 0 deletions src/components/Form/TextArea/TextArea.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { render } from '@testing-library/react';
import React, { createRef } from 'react';
import { TextArea } from './TextArea';

describe('Textarea', () => {
it('renders a textarea element and sets child text', () => {
render(<TextArea>Hello world!</TextArea>);
const container = document.querySelector('.mvn--text-area');
const textarea = document.querySelector('textarea');
expect(container).toBeInTheDocument();
expect(textarea).toBeInTheDocument();
expect(container).toHaveTextContent('Hello world!');
});

it('sets className', () => {
render(<TextArea className="textarea-class">Hello world!</TextArea>);
const element = document.querySelector('.mvn--text-area');
expect(element).toHaveClass('textarea-class');
});

it('passes props', () => {
render(
<TextArea id="TextareaId" rows={4} data-test="textarea-data">
Hello world!
</TextArea>
);
const element = document.querySelector('textarea');
expect(element).toHaveAttribute('id', 'TextareaId');
expect(element).toHaveAttribute('data-test', 'textarea-data');
expect(element).toHaveAttribute('rows', '4');
});

describe('autoSize', () => {
it('is autosize by default', () => {
render(<TextArea />);
const textarea = document.querySelector('textarea');
expect(textarea).not.toHaveClass('no-autosize');
});

it('unset autosize', () => {
render(<TextArea autoSize={false} />);
const textarea = document.querySelector('textarea');
expect(textarea).toHaveClass('no-autosize');
});
});

describe('disabled', () => {
it('is not disabled by default', () => {
render(<TextArea />);
const textarea = document.querySelector('textarea');
expect(textarea).not.toHaveAttribute('disabled');
});

it('sets disabled', () => {
render(<TextArea disabled />);
const textarea = document.querySelector('textarea');
expect(textarea).toHaveAttribute('disabled');
});
});

describe('hasError', () => {
it('has no error styling by default', () => {
render(<TextArea />);
const container = document.querySelector('.mvn--text-area');
expect(container).not.toHaveClass('has-error');
});

it('sets error styling', () => {
render(<TextArea hasError>Error Text</TextArea>);
const container = document.querySelector('.mvn--text-area');
expect(container).toHaveClass('has-error');
});
});

describe('size', () => {
it('is md by default', () => {
render(<TextArea />);
const container = document.querySelector('.mvn--text-area');
expect(container).toHaveClass('md');
expect(container).not.toHaveClass('sm');
expect(container).not.toHaveClass('lg');
});

it('sets sm', () => {
render(<TextArea size="sm" />);
const container = document.querySelector('.mvn--text-area');
expect(container).toHaveClass('sm');
});

it('sets lg', () => {
render(<TextArea size="lg" />);
const container = document.querySelector('.mvn--text-area');
expect(container).toHaveClass('lg');
});
});

describe('forwarding ref', () => {
it('sets ref', () => {
const ref = createRef<HTMLTextAreaElement>();
render(<TextArea ref={ref} />);
const element = document.querySelector('textarea');
expect(ref.current).toBe(element);
});
});
});
101 changes: 101 additions & 0 deletions src/components/Form/TextArea/TextArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import clsx from 'clsx';
import React, { forwardRef, ReactNode, TextareaHTMLAttributes } from 'react';
import TextAreaAutosize from 'react-autosize-textarea';
import { useId } from '../../../hooks';
import { Block } from '../../Block';
import { OptionalField } from '../Form';
import classes from './text-area.module.scss';

/**
* TextArea allows for multiline text input.
*/
export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
(
{
autoSize = true,
children,
className,
disabled = false,
hasError = false,
label,
maxRows,
size = 'md',
style,
rows = 3,
...props
},
ref
) => {
const id = useId(props);
const labelId = useId();

return (
<OptionalField
hasError={hasError}
htmlFor={id}
label={label}
labelId={labelId}
size={size}
>
<div
className={clsx(
'mvn--text-area',
classes.container,
classes[size],
{ [classes['has-error']]: hasError },
className
)}
style={style}
>
<label className={classes.label}>
{autoSize ? (
<TextAreaAutosize
{...props}
className={classes.textarea}
disabled={disabled}
id={id}
maxRows={maxRows}
ref={ref}
rows={rows}
/>
) : (
<textarea
{...props}
className={clsx(classes.textarea, classes['no-autosize'])}
disabled={disabled}
id={id}
ref={ref}
rows={rows}
/>
)}
</label>
{children ? <Block className={classes.text}>{children}</Block> : null}
</div>
</OptionalField>
);
}
);

export interface TextAreaProps
extends TextareaHTMLAttributes<HTMLTextAreaElement> {
/** Wether textarea grows in size when more lines are added */
autoSize?: boolean;

/** Wether textarea is disabled */
disabled?: boolean;

/** Wether textarea contains an error */
hasError?: boolean;

/** Label text */
label?: ReactNode;

/** Maximum number of rows */
maxRows?: number;

/** (min) number of rows */
rows?: number;

/** TextArea size */
size?: 'sm' | 'md' | 'lg';
}
2 changes: 2 additions & 0 deletions src/components/Form/TextArea/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { TextArea } from './TextArea';
export type { TextAreaProps } from './TextArea';
Loading

0 comments on commit 67140c7

Please sign in to comment.