Skip to content

Commit

Permalink
Input Component updated to v1 API
Browse files Browse the repository at this point in the history
  • Loading branch information
jhchill666 authored and levithomason committed Sep 19, 2016
1 parent 3633853 commit 8f4d24b
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Input } from 'stardust'
export default class InputFluidExample extends Component {
render() {
return (
<Input className='fluid icon' icon='search' placeholder='Search...' />
<Input fluid icon='search' placeholder='Search...' />
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Input } from 'stardust'
export default class InputIconExample extends Component {
render() {
return (
<Input className='icon' icon='search' placeholder='Search...' />
<Input icon='search' placeholder='Search...' />
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default class InputInvertedExample extends Component {
render() {
return (
<Segment inverted>
<Input className='inverted' placeholder='Search...' />
<Input inverted placeholder='Search...' />
</Segment>
)
}
Expand Down
12 changes: 6 additions & 6 deletions docs/app/Examples/elements/Input/Variations/InputSizeExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ export default class InputSizeExample extends Component {
render() {
return (
<div>
<Input className='mini icon' icon='search' placeholder='Search...' />
<Input className='mini' icon='search' placeholder='Search...' />
<br />
<Input className='small icon' icon='search' placeholder='Search...' />
<Input className='small' icon='search' placeholder='Search...' />
<br />
<Input className='large icon' icon='search' placeholder='Search...' />
<Input className='large' icon='search' placeholder='Search...' />
<br />
<Input className='big icon' icon='search' placeholder='Search...' />
<Input className='big' icon='search' placeholder='Search...' />
<br />
<Input className='huge icon' icon='search' placeholder='Search...' />
<Input className='huge' icon='search' placeholder='Search...' />
<br />
<Input className='massive icon' icon='search' placeholder='Search...' />
<Input className='massive' icon='search' placeholder='Search...' />
<br />
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Input } from 'stardust'
export default class InputTransparentExample extends Component {
render() {
return (
<Input className='transparent' placeholder='Search...' />
<Input transparent placeholder='Search...' />
)
}
}
192 changes: 119 additions & 73 deletions src/elements/Input/Input.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import _ from 'lodash'
import classNames from 'classnames'
import React, { Component, PropTypes, Children } from 'react'
import React, { PropTypes, Children } from 'react'
import cx from 'classnames'

import { getElementType, getUnhandledProps, META } from '../../lib'
import { Icon } from '../../elements'
import {
getElementType,
getUnhandledProps,
META,
SUI,
useKeyOnly,
} from '../../lib'
import Icon from '../../elements/Icon/Icon'

const inputPropNames = [
// React
Expand Down Expand Up @@ -43,73 +49,113 @@ const inputPropNames = [
* An Input is a field used to elicit a response from a user
* @see Form
*/
export default class Input extends Component {
static propTypes = {
/** An element type to render as (string or function). */
as: PropTypes.oneOfType([
PropTypes.string,
PropTypes.func,
]),

children: PropTypes.node,
className: PropTypes.string,
icon: PropTypes.string,
type: PropTypes.string,
}

static defaultProps = {
type: 'text',
}

static _meta = {
name: 'Input',
type: META.TYPES.ELEMENT,
}

render() {
const { className, children, icon, type } = this.props
// Semantic supports actions and labels on either side of an input.
// The element must be on the same side as the indicated class.
// We first determine the left/right classes for each type of child,
// then we extract the children and place them on the correct side
// of the input.
const isLeftAction = _.includes(className, 'left action')
const isRightAction = !isLeftAction && _.includes(className, 'action')
const isRightLabeled = _.includes(className, 'right labeled')
const isLeftLabeled = !isRightLabeled && _.includes(className, 'labeled')

const labelChildren = []
const actionChildren = []

Children.forEach(children, child => {
const isAction = _.includes(['Button', 'Dropdown', 'Select'], child.type._meta.name)
const isLabel = child.type._meta.name === 'Label'

if (isAction) {
actionChildren.push(child)
} else if (isLabel) {
labelChildren.push(child)
}
})

const classes = classNames(
'ui',
className,
'input'
)
const unhandledProps = getUnhandledProps(Input, this.props)
const inputProps = _.pick(unhandledProps, inputPropNames)
const rest = _.omit(unhandledProps, inputPropNames)
const ElementType = getElementType(Input, this.props)
return (
<ElementType {...rest} className={classes}>
{isLeftLabeled && labelChildren}
{isLeftAction && actionChildren}
<input {...inputProps} type={type} />
{icon && <Icon name={icon} />}
{isRightLabeled && labelChildren}
{isRightAction && actionChildren}
</ElementType>
)
}
function Input(props) {
const {
disabled, error, fluid, inverted, loading, size, transparent,
icon, type, children, className,
} = props

// Semantic supports actions and labels on either side of an input.
// The element must be on the same side as the indicated class.
// We first determine the left/right classes for each type of child,
// then we extract the children and place them on the correct side
// of the input.
const isLeftAction = _.includes(className, 'left action')
const isRightAction = !isLeftAction && _.includes(className, 'action')
const isRightLabeled = _.includes(className, 'right labeled')
const isLeftLabeled = !isRightLabeled && _.includes(className, 'labeled')

const labelChildren = []
const actionChildren = []

Children.forEach(children, child => {
const isButton = child.type.name === 'Button'
const isDropdown = child.type.name === 'Dropdown'
const isLabel = child.type.name === 'Label'
const childIsAction = !isLabel && isButton || isDropdown

if (childIsAction) {
actionChildren.push(child)
} else if (isLabel) {
labelChildren.push(child)
}
})

const classes = cx(
'ui',
size,
useKeyOnly(disabled, 'disabled'),
useKeyOnly(error, 'error'),
useKeyOnly(fluid, 'fluid'),
useKeyOnly(inverted, 'inverted'),
useKeyOnly(loading, 'loading'),
useKeyOnly(transparent, 'transparent'),
icon && 'icon',
className,
'input',
)

const unhandledProps = getUnhandledProps(Input, this.props)
const inputProps = _.pick(unhandledProps, inputPropNames)
const rest = _.omit(unhandledProps, inputPropNames)
const ElementType = getElementType(Input, this.props)
return (
<ElementType {...rest} className={classes}>
{isLeftLabeled && labelChildren}
{isLeftAction && actionChildren}
<input {...inputProps} type={type} />
{icon && <Icon name={icon} />}
{isRightLabeled && labelChildren}
{isRightAction && actionChildren}
</ElementType>
)
}

Input._meta = {
name: 'Input',
type: META.TYPES.ELEMENT,
props: {
size: SUI.SIZES,
},
}

Input.propTypes = {
/** Body of the component. */
children: PropTypes.node,

/** Class names for custom styling. */
className: PropTypes.string,

/** An input field can show that it is disabled */
disabled: PropTypes.bool,

/** An input field can show the data contains errors */
error: PropTypes.bool,

/** Take on the size of it's container */
fluid: PropTypes.bool,

/** Optional icon to display in input */
icon: PropTypes.string,

/** Format to appear on dark backgrounds */
inverted: PropTypes.bool,

/** An icon input field can show that it is currently loading data */
loading: PropTypes.bool,

/** An input can vary in size */
size: PropTypes.oneOf(Input._meta.props.size),

/** Transparent input has no background */
transparent: PropTypes.bool,

/** Specifies the type of <input> element to display */
type: PropTypes.string,
}

Input.defaultProps = {
type: 'text',
}

export default Input
15 changes: 12 additions & 3 deletions test/specs/elements/Input/Input-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,23 @@ import React from 'react'
import Input from 'src/elements/Input/Input'
import * as common from 'test/specs/commonTests'

describe('Input', () => {
describe.only('Input', () => {
common.isConformant(Input)
common.hasUIClassName(Input)
// TODO: inputs do not render child text, only child components in special cases
// see component and find solution
// perhaps splitting rendersChildText() and rendersChildComponents()
// common.rendersChildren(Input)

common.propKeyOnlyToClassName(Input, 'disabled')
common.propKeyOnlyToClassName(Input, 'error')
common.propKeyOnlyToClassName(Input, 'fluid')
common.propKeyOnlyToClassName(Input, 'inverted')
common.propKeyOnlyToClassName(Input, 'loading')
common.propKeyOnlyToClassName(Input, 'transparent')

common.propValueOnlyToClassName(Input, 'size')

it('has the input type of text by default', () => {
shallow(<Input />)
.find('input')
Expand All @@ -35,8 +44,8 @@ describe('Input', () => {
.should.have.prop('name', 'emailAddress')
})

it('adds an Icon given an icon class and prop', () => {
shallow(<Input className='icon' icon='linkedin' />)
it('adds an Icon given prop, but no class', () => {
shallow(<Input icon='linkedin' />)
.should.have.descendants('Icon')
})
})

0 comments on commit 8f4d24b

Please sign in to comment.