Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

feat(Input): adding variation for clearable input #37

Merged
merged 12 commits into from
Aug 21, 2018
Merged
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add `color` variables to Header and Header.Description @kuzhelov ([#72](https://github.com/stardust-ui/react/pull/72))
- Add `ItemLayout` component @mnajdova ([#60](https://github.com/stardust-ui/react/pull/60))

## Features
- Add Input `clearable` prop @alinais ([#37](https://github.com/stardust-ui/react/pull/37))

<!--------------------------------[ v0.2.6 ]------------------------------- -->
## [v0.2.6](https://github.com/stardust-ui/react/tree/v0.2.6) (2018-08-09)
[Compare changes](https://github.com/stardust-ui/react/compare/v0.2.5...v0.2.6)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import * as React from 'react'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No TS in examples please.

import { Input } from '@stardust-ui/react'

const InputExample = () => <Input placeholder="Search..." />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as React from 'react'
import { Input } from '@stardust-ui/react'

const InputExampleClearableShorthand = () => <Input clearable placeholder="Search..." />

export default InputExampleClearableShorthand
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import * as React from 'react'
import { Input } from '@stardust-ui/react'

const InputExample = () => <Input fluid icon="search" placeholder="Search..." />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import * as React from 'react'
import { Input } from '@stardust-ui/react'

const InputExampleIcon = () => <Input icon="search" placeholder="Search..." />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as React from 'react'
import { Input } from '@stardust-ui/react'

const InputExampleIconClearableShorthand = () => (
<Input icon="search" clearable placeholder="Search..." />
)

export default InputExampleIconClearableShorthand
12 changes: 11 additions & 1 deletion docs/src/examples/components/Input/Variations/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import * as React from 'react'
import ComponentExample from 'docs/src/components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/src/components/ComponentDoc/ExampleSection'

Expand All @@ -14,6 +14,16 @@ const Variations = () => (
description="An input can take the full width of the parent element."
examplePath="components/Input/Variations/InputExampleFluid"
/>
<ComponentExample
title="Clearable"
description="An input can be clearable."
examplePath="components/Input/Variations/InputExampleClearable"
/>
<ComponentExample
title="Clearable with icon"
description="An input with a given icon can be clearable (the given icon will change into clear button on typing)."
examplePath="components/Input/Variations/InputExampleIconClearable"
/>
</ExampleSection>
)

Expand Down
64 changes: 57 additions & 7 deletions src/components/Input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as React from 'react'
import * as _ from 'lodash'

import {
AutoControlledComponent,
childrenExist,
createHTMLInput,
customPropTypes,
Expand All @@ -16,7 +17,7 @@ import Icon from '../Icon'
* An Input
* @accessibility This is example usage of the accessibility tag.
*/
class Input extends UIComponent<any, any> {
class Input extends AutoControlledComponent<any, any> {
static className = 'ui-input'

static displayName = 'Input'
Expand All @@ -31,6 +32,12 @@ class Input extends UIComponent<any, any> {
/** Additional classes. */
className: PropTypes.string,

/** A property that will change the icon on the input and clear the input on click on Cancel */
clearable: PropTypes.bool,

/** The default value of the input. */
defaultValue: PropTypes.string,

/** A button can take the width of its container. */
fluid: PropTypes.bool,

Expand All @@ -40,12 +47,23 @@ class Input extends UIComponent<any, any> {
/** Shorthand for creating the HTML Input. */
input: customPropTypes.itemShorthand,

/**
* Called on change.
*
* @param {SyntheticEvent} event - React's original SyntheticEvent.
* @param {object} data - All props and proposed value.
*/
onChange: PropTypes.func,

/** The HTML input type. */
type: PropTypes.string,

/** Custom styles to be applied for component. */
styles: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),

/** The value of the input. */
value: PropTypes.string,

/** Custom variables to be applied for component. */
variables: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
}
Expand All @@ -54,11 +72,15 @@ class Input extends UIComponent<any, any> {
'as',
'children',
'className',
'clearable',
'defaultValue',
'fluid',
'icon',
'input',
'onChange',
'styles',
'type',
'value',
'variables',
]

Expand All @@ -67,11 +89,18 @@ class Input extends UIComponent<any, any> {
type: 'text',
}

static autoControlledProps = ['value']

inputRef: any

computeTabIndex = props => {
if (!_.isNil(props.tabIndex)) return props.tabIndex
if (props.onClick) return 0
state: any = { value: this.props.value || this.props.defaultValue || '' }

handleChange = e => {
const value = _.get(e, 'target.value')

_.invoke(this.props, 'onChange', e, { ...this.props, value })

this.trySetState({ value })
}

handleChildOverrides = (child, defaultProps) => ({
Expand All @@ -81,43 +110,64 @@ class Input extends UIComponent<any, any> {

handleInputRef = c => (this.inputRef = c)

handleOnClear = e => {
const { clearable } = this.props
const { value } = this.state

if (clearable) {
this.trySetState({ value: '' })
}
}

partitionProps = () => {
const { type } = this.props
const { value } = this.state

const unhandled = getUnhandledProps(Input, this.props)
const [htmlInputProps, rest] = partitionHTMLProps(unhandled)

return [
{
...htmlInputProps,
onChange: this.handleChange,
type,
value: value || '',
},
rest,
]
}

computeIcon = () => {
const { icon } = this.props
const { clearable, icon } = this.props
const { value } = this.state

if (clearable && value.length !== 0) {
return 'close'
}

if (!_.isNil(icon)) return icon

return null
}

handleIconOverrides = predefinedProps => {
return {
onClick: e => {
this.handleOnClear(e)

this.inputRef.focus()
_.invoke(predefinedProps, 'onClick', e, this.props)
},
tabIndex: this.computeTabIndex,
...(predefinedProps.onClick && { tabIndex: '0' }),
}
}

renderComponent({ ElementType, classes, rest, styles }) {
const { children, input, type } = this.props
const { children, clearable, input, type } = this.props
const [htmlInputProps, restProps] = this.partitionProps()

const inputClasses = classes.input
const iconClasses = classes.icon

// Render with children
// ----------------------------------------
Expand Down
10 changes: 0 additions & 10 deletions test/specs/components/Input/Input-test.ts

This file was deleted.

24 changes: 24 additions & 0 deletions test/specs/components/Input/Input-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from 'react'

import { isConformant } from 'test/specs/commonTests'

import Input from 'src/components/Input/Input'
import { mountWithProvider } from 'test/utils'

describe('Input', () => {
isConformant(Input)

describe('input', () => {
it('renders a text <input> by default', () => {
const input = mountWithProvider(<Input placeholder="Search ..." />).find('input[type="text"]')
expect(input).not.toBe(undefined)
})
})

describe('icon', () => {
it('creates the Icon component when the icon shorthand is provided', () => {
const input = mountWithProvider(<Input icon={{ name: 'close' }} />).find('Icon[name="close"]')
expect(input).not.toBe(undefined)
})
})
})