diff --git a/docs/app/Examples/elements/Input/Variations/InputFluidExample.js b/docs/app/Examples/elements/Input/Variations/InputFluidExample.js index f69f9778c7..b91031688d 100644 --- a/docs/app/Examples/elements/Input/Variations/InputFluidExample.js +++ b/docs/app/Examples/elements/Input/Variations/InputFluidExample.js @@ -4,7 +4,7 @@ import { Input } from 'stardust' export default class InputFluidExample extends Component { render() { return ( - + ) } } diff --git a/docs/app/Examples/elements/Input/Variations/InputIconExample.js b/docs/app/Examples/elements/Input/Variations/InputIconExample.js index a56f20c3f4..13e3b55f41 100644 --- a/docs/app/Examples/elements/Input/Variations/InputIconExample.js +++ b/docs/app/Examples/elements/Input/Variations/InputIconExample.js @@ -4,7 +4,7 @@ import { Input } from 'stardust' export default class InputIconExample extends Component { render() { return ( - + ) } } diff --git a/docs/app/Examples/elements/Input/Variations/InputInvertedExample.js b/docs/app/Examples/elements/Input/Variations/InputInvertedExample.js index c81efec052..8e5b6d6e32 100644 --- a/docs/app/Examples/elements/Input/Variations/InputInvertedExample.js +++ b/docs/app/Examples/elements/Input/Variations/InputInvertedExample.js @@ -5,7 +5,7 @@ export default class InputInvertedExample extends Component { render() { return ( - + ) } diff --git a/docs/app/Examples/elements/Input/Variations/InputSizeExample.js b/docs/app/Examples/elements/Input/Variations/InputSizeExample.js index 7bece9a1a0..ec5de6831d 100644 --- a/docs/app/Examples/elements/Input/Variations/InputSizeExample.js +++ b/docs/app/Examples/elements/Input/Variations/InputSizeExample.js @@ -5,17 +5,17 @@ export default class InputSizeExample extends Component { render() { return (
- +
- +
- +
- +
- +
- +
) diff --git a/docs/app/Examples/elements/Input/Variations/InputTransparentExample.js b/docs/app/Examples/elements/Input/Variations/InputTransparentExample.js index 9ab2ebb87c..4c6b04914e 100644 --- a/docs/app/Examples/elements/Input/Variations/InputTransparentExample.js +++ b/docs/app/Examples/elements/Input/Variations/InputTransparentExample.js @@ -4,7 +4,7 @@ import { Input } from 'stardust' export default class InputTransparentExample extends Component { render() { return ( - + ) } } diff --git a/src/elements/Input/Input.js b/src/elements/Input/Input.js index a58c242842..cc8292c6da 100644 --- a/src/elements/Input/Input.js +++ b/src/elements/Input/Input.js @@ -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 @@ -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 ( - - {isLeftLabeled && labelChildren} - {isLeftAction && actionChildren} - - {icon && } - {isRightLabeled && labelChildren} - {isRightAction && actionChildren} - - ) - } +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 ( + + {isLeftLabeled && labelChildren} + {isLeftAction && actionChildren} + + {icon && } + {isRightLabeled && labelChildren} + {isRightAction && actionChildren} + + ) } + +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 element to display */ + type: PropTypes.string, +} + +Input.defaultProps = { + type: 'text', +} + +export default Input diff --git a/test/specs/elements/Input/Input-test.js b/test/specs/elements/Input/Input-test.js index 21cf1226a3..4bb11ff617 100644 --- a/test/specs/elements/Input/Input-test.js +++ b/test/specs/elements/Input/Input-test.js @@ -3,7 +3,7 @@ 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 @@ -11,6 +11,15 @@ describe('Input', () => { // 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() .find('input') @@ -35,8 +44,8 @@ describe('Input', () => { .should.have.prop('name', 'emailAddress') }) - it('adds an Icon given an icon class and prop', () => { - shallow() + it('adds an Icon given prop, but no class', () => { + shallow() .should.have.descendants('Icon') }) })