-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #103 from kodiak-packages/textarea
Add textarea component
- Loading branch information
Showing
8 changed files
with
247 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
--- | ||
name: TextArea | ||
menu: Components | ||
route: /text-area | ||
--- | ||
|
||
import { Playground, Props } from 'docz'; | ||
import { TextArea } from '../../index.ts' | ||
|
||
# TextArea | ||
|
||
A Input can be used to let the user insert text using key strokes. A crucial form item for building interactive apps. | ||
|
||
## Best practices | ||
|
||
- TextAreas should be wrapped in a [FormField](/form-field) or should be preceded by another component serving as a label | ||
|
||
## Examples | ||
|
||
### Basic usage | ||
|
||
<Playground> | ||
<TextArea name="description" /> | ||
</Playground> | ||
|
||
### Listening for changes | ||
|
||
<Playground> | ||
<TextArea name="changeLogger" onChange={(event) => console.log(`LOGGER: ${event.target.value}`)} /> | ||
</Playground> | ||
|
||
### Input with error | ||
|
||
<Playground> | ||
<TextArea name="invalidTextArea" isInvalid /> | ||
</Playground> | ||
|
||
## API | ||
|
||
<Props of={TextArea} /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
.textarea { | ||
background-color: var(--color-neutral-1); | ||
border-radius: var(--border-radius-small); | ||
border: 1px solid var(--color-neutral-4); | ||
padding: 4.5px 14px; | ||
width: 100%; | ||
color: var(--color-neutral-9); | ||
resize: vertical; | ||
} | ||
|
||
.textarea::placeholder { | ||
color: var(--color-neutral-6); | ||
} | ||
|
||
.textarea:focus { | ||
outline: none; | ||
box-shadow: 0 0 0 3px var(--color-focus); | ||
} | ||
|
||
.containsError { | ||
border-color: var(--color-error); | ||
} | ||
|
||
.containsError:focus { | ||
box-shadow: 0 0 0 3px var(--color-error-light); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import React, { ComponentProps } from 'react'; | ||
import { fireEvent, render } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
|
||
import TextArea from './TextArea'; | ||
|
||
describe('TextArea', () => { | ||
const value = 'It was nice!'; | ||
const defaultButtonProps: ComponentProps<typeof TextArea> = { | ||
name: 'comment', | ||
}; | ||
|
||
test('default snapshot', () => { | ||
const component = <TextArea {...defaultButtonProps} />; | ||
const { asFragment } = render(component); | ||
expect(asFragment()).toMatchSnapshot(); | ||
}); | ||
|
||
test('value prop', () => { | ||
const component = <TextArea {...defaultButtonProps} value={value} onChange={() => {}} />; | ||
const { getByTestId } = render(component); | ||
const textAreaElement = getByTestId('textarea-comment'); | ||
|
||
expect(textAreaElement.innerHTML).toBe(value); | ||
}); | ||
|
||
test('onChange prop', async () => { | ||
const onChangeFn = jest.fn(); | ||
const component = <TextArea {...defaultButtonProps} onChange={onChangeFn} />; | ||
const { getByTestId } = render(component); | ||
const textAreaElement = getByTestId('textarea-comment'); | ||
|
||
await userEvent.type(textAreaElement, value); | ||
|
||
expect(onChangeFn).toHaveBeenCalledTimes(value.length); | ||
}); | ||
|
||
test('onBlur prop', async () => { | ||
const onBlurFn = jest.fn(); | ||
const component = <TextArea {...defaultButtonProps} onBlur={onBlurFn} />; | ||
const { getByTestId } = render(component); | ||
const textAreaElement = getByTestId('textarea-comment'); | ||
|
||
fireEvent.blur(textAreaElement); | ||
|
||
expect(onBlurFn).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
test('placeholder prop', () => { | ||
const component = <TextArea {...defaultButtonProps} placeholder="First name" />; | ||
const { getByTestId } = render(component); | ||
const textAreaElement = getByTestId('textarea-comment'); | ||
|
||
expect(textAreaElement.getAttribute('placeholder')).toBe('First name'); | ||
}); | ||
|
||
test('errorMessage prop', () => { | ||
const component = <TextArea {...defaultButtonProps} isInvalid />; | ||
const { getByTestId } = render(component); | ||
const textAreaElement = getByTestId('textarea-comment'); | ||
|
||
expect(textAreaElement.classList).toContain('containsError'); | ||
}); | ||
|
||
test('spellCheck prop', () => { | ||
const component = <TextArea {...defaultButtonProps} spellCheck />; | ||
const { getByTestId } = render(component); | ||
const textAreaElement = getByTestId('textarea-comment'); | ||
|
||
expect(textAreaElement.hasAttribute('spellcheck')).toBe(true); | ||
expect(textAreaElement.getAttribute('spellcheck')).not.toBe(false); | ||
}); | ||
|
||
test('autoComplete prop', () => { | ||
const component = <TextArea {...defaultButtonProps} autoComplete />; | ||
const { getByTestId } = render(component); | ||
const textAreaElement = getByTestId('textarea-comment'); | ||
|
||
expect(textAreaElement.getAttribute('autocomplete')).toBe('on'); | ||
}); | ||
|
||
test('maxLength prop', () => { | ||
const maxLength = 3; | ||
const component = <TextArea {...defaultButtonProps} maxLength={3} />; | ||
const { getByTestId } = render(component); | ||
const textAreaElement = getByTestId('textarea-comment'); | ||
|
||
expect(textAreaElement.getAttribute('maxlength')).toBe(maxLength.toString()); | ||
}); | ||
|
||
test('className prop', () => { | ||
const className = 'center'; | ||
const component = <TextArea {...defaultButtonProps} className={className} />; | ||
const { getByTestId } = render(component); | ||
const containerElement = getByTestId('textarea-comment'); | ||
|
||
const renderedClassNames = containerElement.className.split(' '); | ||
expect(renderedClassNames).toContain(className); | ||
// className in prop should be the last in the row | ||
expect(renderedClassNames.indexOf(className)).toBe(renderedClassNames.length - 1); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import React, { ChangeEventHandler, FocusEventHandler } from 'react'; | ||
import classNames from 'classnames'; | ||
|
||
import cssReset from '../../css-reset.module.css'; | ||
import styles from './TextArea.module.css'; | ||
|
||
interface Props { | ||
name: string; | ||
value?: string; | ||
onChange?: ChangeEventHandler<HTMLTextAreaElement>; | ||
onBlur?: FocusEventHandler<HTMLTextAreaElement>; | ||
placeholder?: string; | ||
spellCheck?: boolean; | ||
autoComplete?: boolean; | ||
maxLength?: number; | ||
className?: string; | ||
isInvalid?: boolean; | ||
} | ||
|
||
const TextArea = React.forwardRef<HTMLTextAreaElement, Props>( | ||
( | ||
{ | ||
name, | ||
value, | ||
onChange, | ||
onBlur, | ||
placeholder, | ||
isInvalid = false, | ||
spellCheck = false, | ||
autoComplete = false, | ||
maxLength, | ||
className, | ||
}: Props, | ||
ref, | ||
) => { | ||
const textareaClassNames = classNames( | ||
cssReset.ventura, | ||
styles.textarea, | ||
{ | ||
[styles.containsError]: Boolean(isInvalid), | ||
}, | ||
className, | ||
); | ||
|
||
return ( | ||
<textarea | ||
name={name} | ||
value={value} | ||
onChange={onChange} | ||
placeholder={placeholder} | ||
spellCheck={spellCheck} | ||
autoComplete={autoComplete ? 'on' : 'off'} | ||
maxLength={maxLength} | ||
ref={ref} | ||
className={textareaClassNames} | ||
data-testid={`textarea-${name}`} | ||
onBlur={onBlur} | ||
/> | ||
); | ||
}, | ||
); | ||
|
||
export default TextArea; |
13 changes: 13 additions & 0 deletions
13
src/components/TextArea/__snapshots__/TextArea.test.tsx.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`TextArea default snapshot 1`] = ` | ||
<DocumentFragment> | ||
<textarea | ||
autocomplete="off" | ||
class="ventura textarea" | ||
data-testid="textarea-comment" | ||
name="comment" | ||
spellcheck="false" | ||
/> | ||
</DocumentFragment> | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters