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 9, 2016
1 parent da0017d commit 838159a
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 78 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ dist/
docs/build
docs/app/docgenInfo.json
dll/
.DS_Store
.idea
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 className='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...' />
)
}
}
1 change: 1 addition & 0 deletions src/elements/Image/Image.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default class Image extends Component {

render() {
const classes = classNames(
'sd-image',
'ui',
this.props.className,
'image'
Expand Down
181 changes: 116 additions & 65 deletions src/elements/Input/Input.js
Original file line number Diff line number Diff line change
@@ -1,70 +1,121 @@
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 META from '../../utils/Meta'
import { getUnhandledProps } from '../../utils/propUtils'
import * as sui from '../../utils/semanticUtils'
import {
getUnhandledProps,
useKeyOnly,
} from '../../utils/propUtils'
import Icon from '../../elements/Icon/Icon'

export default class Input extends Component {
static propTypes = {
children: PropTypes.node,
className: PropTypes.string,
icon: PropTypes.string,
type: PropTypes.string,
}

static defaultProps = {
type: 'text',
}

static _meta = {
library: META.library.semanticUI,
name: 'Input',
type: META.type.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 props = getUnhandledProps(Input, this.props)

return (
<div className={classes}>
{isLeftLabeled && labelChildren}
{isLeftAction && actionChildren}
<input {...props} type={type} />
{icon && <Icon className={icon} />}
{isRightLabeled && labelChildren}
{isRightAction && actionChildren}
</div>
)
}
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'
// TODO: use child.type.name === 'Label' once Label component is merged.
const isLabel = _.isString(child.props.className) && !!child.props.className.match(/ui.*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 rest = getUnhandledProps(Input, props)

return (
<div className={classes}>
{isLeftLabeled && labelChildren}
{isLeftAction && actionChildren}
<input type={type} {...rest} />
{icon && <Icon className={icon} />}
{isRightLabeled && labelChildren}
{isRightAction && actionChildren}
</div>
)
}

Input._meta = {
library: META.library.semanticUI,
name: 'Input',
type: META.type.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 838159a

Please sign in to comment.