Skip to content

Commit

Permalink
feat(Input): Add child component TextArea
Browse files Browse the repository at this point in the history
  • Loading branch information
xinyao27 committed Sep 28, 2019
1 parent 484e5f6 commit d04f622
Show file tree
Hide file tree
Showing 11 changed files with 409 additions and 2 deletions.
30 changes: 30 additions & 0 deletions packages/fluent-ui.com/src/docs/components/Input/Input.md
Expand Up @@ -97,3 +97,33 @@ Show a button to clear your input.
<Input ghost />
</Box>
```

## TextArea

```jsx
<Box padding={20} background="url(https://i.loli.net/2019/06/08/5cfb6d5a7456419123.jpg) center/cover fixed">
<Input.TextArea
rows={4}
placeholder="default"
resize="none"
/>
<Input.TextArea
rows={4}
placeholder="ghost"
ghost
resize="horizontal"
/>
<Input.TextArea
rows={4}
placeholder="error"
error
resize="vertical"
/>
<Input.TextArea
rows={4}
placeholder="disabled"
disabled
resize="none"
/>
</Box>
```
30 changes: 30 additions & 0 deletions packages/fluent-ui.com/src/docs/components/Input/Input.zh.md
Expand Up @@ -97,3 +97,33 @@ langKey: "zh"
<Input ghost />
</Box>
```

## TextArea

```jsx
<Box padding={20} background="url(https://i.loli.net/2019/06/08/5cfb6d5a7456419123.jpg) center/cover fixed">
<Input.TextArea
rows={4}
placeholder="default"
resize="none"
/>
<Input.TextArea
rows={4}
placeholder="ghost"
ghost
resize="horizontal"
/>
<Input.TextArea
rows={4}
placeholder="error"
error
resize="vertical"
/>
<Input.TextArea
rows={4}
placeholder="disabled"
disabled
resize="none"
/>
</Box>
```
14 changes: 14 additions & 0 deletions packages/fluent-ui.com/src/docs/components/Input/api.md
Expand Up @@ -15,6 +15,8 @@ import { Input } from '@fluent-ui/core'

### Props

#### Input

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| value | string | | The value of the `input` element, required for a controlled component. |
Expand All @@ -27,3 +29,15 @@ import { Input } from '@fluent-ui/core'
| prefix | React.ReactNode | null | Pre-content input box. |
| suffix | React.ReactNode | null | Rear content input box. |
| ghost | boolean | | Ghost button, background transparent. |

#### Input.TextArea

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| value | string | | The value of the `input` element, required for a controlled component. |
| onChange | (value: string) => void | | Callback fired when the value is changed. |
| placeholder | string | | The short hint displayed in the input before the user enters a value. |
| disabled | boolean | | If `true`, the `input` element will be disabled. |
| error | boolean | | If `true`, the `input` will indicate an error. |
| ghost | boolean | | Ghost button, background transparent. |
| resize | 'none' &or; 'both' &or; 'horizontal' &or; 'vertical' &or; 'block' &or; 'inline' | 'both' | The `resize` prop sets whether an `TextArea` is resizable, and if so, in which directions. |
14 changes: 14 additions & 0 deletions packages/fluent-ui.com/src/docs/components/Input/api.zh.md
Expand Up @@ -15,6 +15,8 @@ import { Input } from '@fluent-ui/core'

### Props

#### Input

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| value | string | | 输入元素的值,受控组件必填。 |
Expand All @@ -27,3 +29,15 @@ import { Input } from '@fluent-ui/core'
| prefix | React.ReactNode | null | 输入框的前置内容。 |
| suffix | React.ReactNode | null | 输入框的后置内容。 |
| ghost | boolean | | 幽灵按钮,背景透明。 |

#### Input.TextArea

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| value | string | | 输入元素的值,受控组件必填。 |
| onChange | (value: string) => void | | 输入框内容改变时的回调。 |
| placeholder | string | | 在用户输入值之前在输入框中显示的简短提示。 |
| disabled | boolean | | 如果 `true`, `input` 元素将被禁用。 |
| error | boolean | | 如果 `true`, `input` 将会表示为错误状态。 |
| ghost | boolean | | 幽灵按钮,背景透明。 |
| resize | 'none' &or; 'both' &or; 'horizontal' &or; 'vertical' &or; 'block' &or; 'inline' | 'both' | `resize` 属性允许你控制 `TextArea` 的可调整大小性。|
11 changes: 9 additions & 2 deletions packages/fluent-ui/src/Input/Input.tsx
Expand Up @@ -3,10 +3,11 @@ import classNames from 'classnames'
import { createUseStyles } from '@fluent-ui/styles'
import { styles } from './Input.styled'
import { Theme } from '../styles'
import { InputClassProps, InputProps, InputPropTypes } from './Input.type'
import { InputClassProps, InputProps, InputPropTypes, InputType } from './Input.type'

import { CloseLine as CloseLineIconIcon } from '@fluent-ui/icons'
import Transition from '../Transition'
import TextArea from './components/TextArea'

export const name = 'Input'

Expand Down Expand Up @@ -99,6 +100,12 @@ const Input: React.FC<InputProps> = React.forwardRef<HTMLInputElement, InputProp
}
)

Object.defineProperty(Input, 'TextArea', {
get(): typeof TextArea {
return TextArea
}
})

Input.displayName = `F${name}`

Input.propTypes = InputPropTypes
Expand All @@ -108,4 +115,4 @@ Input.defaultProps = {
suffix: null
}

export default Input
export default Input as InputType
5 changes: 5 additions & 0 deletions packages/fluent-ui/src/Input/Input.type.ts
@@ -1,6 +1,7 @@
import * as React from 'react'
import * as PropTypes from 'prop-types'
import { StandardProps } from '..'
import TextArea from './components/TextArea'

export type InputClassProps =
| 'root'
Expand Down Expand Up @@ -41,3 +42,7 @@ export const InputPropTypes = {
suffix: PropTypes.node,
ghost: PropTypes.bool
}

export interface InputType extends React.ForwardRefExoticComponent<InputProps> {
TextArea: typeof TextArea
}
60 changes: 60 additions & 0 deletions packages/fluent-ui/src/Input/__tests__/TextArea.test.tsx
@@ -0,0 +1,60 @@
import * as React from 'react'
import { fireEvent } from '@testing-library/react'
import { getClasses, render } from '../../test-utils'
import '@testing-library/jest-dom/extend-expect'

import Input from '../Input'
import { name } from '../components/TextArea'
import { styles } from '../components/TextArea.styled'
import { TextAreaClassProps } from '../components/TextArea.type'

const classes = getClasses<TextAreaClassProps>(styles, name)

describe('Input.TextArea', (): void => {
const text = 'Hint Text'

test('should be support ref', (): void => {
const ref = React.createRef<HTMLTextAreaElement>()
const { getByTestId } = render(<Input.TextArea ref={ref} data-testid="textarea" />)
const { current: element } = ref
expect(element).toEqual(getByTestId('textarea'))
})

test('should be controlled', (): void => {
const ref = React.createRef<HTMLTextAreaElement>()
let value = ''
function handleChange(v: string): void {
value = v
}
render(<Input.TextArea ref={ref} value={value} onChange={handleChange} />)
fireEvent.change(ref.current!, { target: { value: text } })
expect(value).toEqual(text)
expect(ref.current!).toHaveClass(classes.root)
})

test('should be support placeholder', (): void => {
const ref = React.createRef<HTMLTextAreaElement>()
render(<Input.TextArea ref={ref} placeholder={text} />)
expect((ref.current as HTMLTextAreaElement).placeholder).toEqual(text)
})

test('should be support disabled', (): void => {
const ref = React.createRef<HTMLTextAreaElement>()
render(<Input.TextArea ref={ref} disabled={true} />)
expect((ref.current as HTMLTextAreaElement).disabled).toEqual(true)
})

test('should be support error', (): void => {
const ref = React.createRef<HTMLTextAreaElement>()
const { sheets } = render(<Input.TextArea ref={ref} error={true} />)
expect(ref.current as HTMLTextAreaElement).toHaveClass(classes.error)
expect(sheets.toString()).toMatchSnapshot()
})

test('should be support ghost', (): void => {
const ref = React.createRef<HTMLTextAreaElement>()
const { sheets } = render(<Input.TextArea ref={ref} ghost={true} />)
expect(ref.current as HTMLTextAreaElement).toHaveClass(classes.ghost)
expect(sheets.toString()).toMatchSnapshot()
})
})
@@ -0,0 +1,95 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Input.TextArea should be support error 1`] = `
".FTextArea-root {
font: inherit;
border: 2px solid;
outline: none;
padding: 6px 12px;
transition: background-color 250ms cubic-bezier(0.76, 0.24, 0.23, 0.94) 0ms,color 250ms cubic-bezier(0.76, 0.24, 0.23, 0.94) 0ms,border-color 250ms cubic-bezier(0.76, 0.24, 0.23, 0.94) 0ms;
border-color: #dedede;
border-radius: 2px;
}
.FTextArea-root:hover {
border-color: #b5b5b5;
}
.FTextArea-root:active, .FTextArea-root:focus {
border-color: #0078d4;
}
.FTextArea-root:disabled {
color: #8c8c8c;
cursor: not-allowed;
pointer-events: none;
background-color: #e9e9e9;
}
.FTextArea-error {
border-color: #f44336;
}
.FTextArea-error:hover {
border-color: #f44336;
}
.FTextArea-error:active, .FTextArea-error:focus {
border-color: #f44336;
}
.FTextArea-ghost {
border-color: rgba(0, 0, 0, 0.1);
background-color: transparent;
}
.FTextArea-ghost:hover {
border-color: rgba(0, 0, 0, 0.2);
}
.FTextArea-ghost:active, .FTextArea-ghost:focus {
border-color: rgba(0, 0, 0, 0.4);
}
.FTextArea-resize {}
.FTextArea-resize-0 {
resize: both;
}"
`;

exports[`Input.TextArea should be support ghost 1`] = `
".FTextArea-root {
font: inherit;
border: 2px solid;
outline: none;
padding: 6px 12px;
transition: background-color 250ms cubic-bezier(0.76, 0.24, 0.23, 0.94) 0ms,color 250ms cubic-bezier(0.76, 0.24, 0.23, 0.94) 0ms,border-color 250ms cubic-bezier(0.76, 0.24, 0.23, 0.94) 0ms;
border-color: #dedede;
border-radius: 2px;
}
.FTextArea-root:hover {
border-color: #b5b5b5;
}
.FTextArea-root:active, .FTextArea-root:focus {
border-color: #0078d4;
}
.FTextArea-root:disabled {
color: #8c8c8c;
cursor: not-allowed;
pointer-events: none;
background-color: #e9e9e9;
}
.FTextArea-error {
border-color: #f44336;
}
.FTextArea-error:hover {
border-color: #f44336;
}
.FTextArea-error:active, .FTextArea-error:focus {
border-color: #f44336;
}
.FTextArea-ghost {
border-color: rgba(0, 0, 0, 0.1);
background-color: transparent;
}
.FTextArea-ghost:hover {
border-color: rgba(0, 0, 0, 0.2);
}
.FTextArea-ghost:active, .FTextArea-ghost:focus {
border-color: rgba(0, 0, 0, 0.4);
}
.FTextArea-resize {}
.FTextArea-resize-0 {
resize: both;
}"
`;
57 changes: 57 additions & 0 deletions packages/fluent-ui/src/Input/components/TextArea.styled.ts
@@ -0,0 +1,57 @@
import { Style, Styles } from 'jss'
import { Theme } from '../../styles'
import { TextAreaClassProps, TextAreaProps } from './TextArea.type'

const root = (theme: Theme): Style => ({
outline: 'none',
font: 'inherit',
borderRadius: 2,
border: '2px solid',
borderColor: theme.colors!.standard!.default,
transition: theme.transitions!.input,
'&:hover': {
borderColor: theme.colors!.standard!.dark1
},
'&:active, &:focus': {
borderColor: theme.colors!.primary!.default
},
'&:disabled': {
color: theme.colors!.standard!.dark2,
backgroundColor: theme.colors!.standard!.light1,
cursor: 'not-allowed',
pointerEvents: 'none'
},
...theme.sizes!.medium!.input
})

const error = (theme: Theme): Style => ({
borderColor: theme.colors!.error!.default,
'&:hover': {
borderColor: theme.colors!.error!.default
},
'&:active, &:focus': {
borderColor: theme.colors!.error!.default
}
})

const ghost = (theme: Theme): Style => ({
backgroundColor: 'transparent',
borderColor: theme.colors!.standard!.transparent1,
'&:hover': {
borderColor: theme.colors!.standard!.transparent2
},
'&:active, &:focus': {
borderColor: theme.colors!.standard!.transparent3
}
})

const resize = ({ resize }: TextAreaProps): Style => ({
resize
})

export const styles = (theme: Theme): Styles<TextAreaClassProps> => ({
root: root(theme),
error: error(theme),
ghost: ghost(theme),
resize
})

0 comments on commit d04f622

Please sign in to comment.