From 63aa83753483a09b7c117d7b88e1adfe8e7a20b3 Mon Sep 17 00:00:00 2001 From: Alexander Fedyashov Date: Wed, 10 Aug 2016 23:15:35 +0300 Subject: [PATCH] Add Step component (#335) * (feat) Rail #181 * (feat) Rail #181 * (feat) Rail docs #181 * (fix) Sort Rail props #181 * (fix) Rail review fixes #181 * (fix) Rail review fixes #181 * (fix) Rail review fixes #181 * (fix) Rail sizes #181 * (fix) Rail sizes in docs #181 * (feat) Step Title * feat(Step) Step component * feat(Step) Step component * feat(Step) Step component * feat(Step) Examples and implements * feat(Step) Fix * fix(Step) Refactor fragment * feat(Step) Shorthand props * feat(Step) More docs and feats * feat(Parts) Title & Description * feat(Parts) Move code to parts * feat(Parts) Step cleanup * fix(Step) Fix doc and prop * fix(Step) Fix for content * fix(Step) Fix examples and components * fix(Step) Fix comment * fix(Step) Fix prop and tests * fix(Step) Content test * feat(Step) Tests for group * feat(Step) Tests for Step * fix(Step) Add test for children * fix(Step) Update example * fix(Step) Remove library from _meta * fix(Step) Fix link example * fix(Step) Update components, docs and tests --- .../elements/Step/Content/Descriptions.js | 32 ++++++ .../Examples/elements/Step/Content/Icons.js | 25 +++++ .../Examples/elements/Step/Content/Links.js | 37 +++++++ .../Examples/elements/Step/Content/index.js | 27 +++++ .../Examples/elements/Step/Groups/Groups.js | 36 ++++++ .../Examples/elements/Step/Groups/Ordered.js | 32 ++++++ .../Examples/elements/Step/Groups/Vertical.js | 36 ++++++ .../Examples/elements/Step/Groups/index.js | 36 ++++++ .../Examples/elements/Step/States/Active.js | 20 ++++ .../elements/Step/States/Completed.js | 20 ++++ .../Examples/elements/Step/States/Disabled.js | 10 ++ .../Examples/elements/Step/States/index.js | 29 +++++ .../app/Examples/elements/Step/Types/Basic.js | 10 ++ .../app/Examples/elements/Step/Types/index.js | 17 +++ .../elements/Step/Variations/Fluid.js | 19 ++++ .../elements/Step/Variations/Sizes.js | 38 +++++++ .../elements/Step/Variations/Stackable.js | 12 ++ .../elements/Step/Variations/index.js | 27 +++++ docs/app/Examples/elements/Step/index.js | 19 ++++ src/elements/Step/Step.js | 104 ++++++++++++++++++ src/elements/Step/StepContent.js | 55 +++++++++ src/elements/Step/StepDescription.js | 38 +++++++ src/elements/Step/StepGroup.js | 80 ++++++++++++++ src/elements/Step/StepTitle.js | 38 +++++++ src/index.js | 1 + test/specs/elements/Step/Step-test.js | 71 ++++++++++++ test/specs/elements/Step/StepContent-test.js | 30 +++++ .../elements/Step/StepDescription-test.js | 26 +++++ test/specs/elements/Step/StepGroup-test.js | 44 ++++++++ test/specs/elements/Step/StepTitle-test.js | 26 +++++ 30 files changed, 995 insertions(+) create mode 100644 docs/app/Examples/elements/Step/Content/Descriptions.js create mode 100644 docs/app/Examples/elements/Step/Content/Icons.js create mode 100644 docs/app/Examples/elements/Step/Content/Links.js create mode 100644 docs/app/Examples/elements/Step/Content/index.js create mode 100644 docs/app/Examples/elements/Step/Groups/Groups.js create mode 100644 docs/app/Examples/elements/Step/Groups/Ordered.js create mode 100644 docs/app/Examples/elements/Step/Groups/Vertical.js create mode 100644 docs/app/Examples/elements/Step/Groups/index.js create mode 100644 docs/app/Examples/elements/Step/States/Active.js create mode 100644 docs/app/Examples/elements/Step/States/Completed.js create mode 100644 docs/app/Examples/elements/Step/States/Disabled.js create mode 100644 docs/app/Examples/elements/Step/States/index.js create mode 100644 docs/app/Examples/elements/Step/Types/Basic.js create mode 100644 docs/app/Examples/elements/Step/Types/index.js create mode 100644 docs/app/Examples/elements/Step/Variations/Fluid.js create mode 100644 docs/app/Examples/elements/Step/Variations/Sizes.js create mode 100644 docs/app/Examples/elements/Step/Variations/Stackable.js create mode 100644 docs/app/Examples/elements/Step/Variations/index.js create mode 100644 docs/app/Examples/elements/Step/index.js create mode 100644 src/elements/Step/Step.js create mode 100644 src/elements/Step/StepContent.js create mode 100644 src/elements/Step/StepDescription.js create mode 100644 src/elements/Step/StepGroup.js create mode 100644 src/elements/Step/StepTitle.js create mode 100644 test/specs/elements/Step/Step-test.js create mode 100644 test/specs/elements/Step/StepContent-test.js create mode 100644 test/specs/elements/Step/StepDescription-test.js create mode 100644 test/specs/elements/Step/StepGroup-test.js create mode 100644 test/specs/elements/Step/StepTitle-test.js diff --git a/docs/app/Examples/elements/Step/Content/Descriptions.js b/docs/app/Examples/elements/Step/Content/Descriptions.js new file mode 100644 index 0000000000..0df71028ec --- /dev/null +++ b/docs/app/Examples/elements/Step/Content/Descriptions.js @@ -0,0 +1,32 @@ +import React from 'react' +import { Step } from 'stardust' + +const { Description, Group, Title } = Step + +const Descriptions = () => ( +
+ + + Shipping + Choose your shipping options + + + +
+ + + + + <Description description='Choose your shipping options' /> + </Step> + </Group> + + <br /> + + <Group> + <Step title='Shipping' description='Choose your shipping options' /> + </Group> + </div> +) + +export default Descriptions diff --git a/docs/app/Examples/elements/Step/Content/Icons.js b/docs/app/Examples/elements/Step/Content/Icons.js new file mode 100644 index 0000000000..82345ba888 --- /dev/null +++ b/docs/app/Examples/elements/Step/Content/Icons.js @@ -0,0 +1,25 @@ +import React from 'react' +import { Icon, Step } from 'stardust' + +const { Content, Description, Group, Title } = Step + +const Icons = () => ( + <Group> + <Step> + <Icon name='truck' /> + <Content> + <Title>Shipping + Choose your shipping options + + + + + + + + + + +) + +export default Icons diff --git a/docs/app/Examples/elements/Step/Content/Links.js b/docs/app/Examples/elements/Step/Content/Links.js new file mode 100644 index 0000000000..757d714440 --- /dev/null +++ b/docs/app/Examples/elements/Step/Content/Links.js @@ -0,0 +1,37 @@ +import React, { Component } from 'react' +import { Step } from 'stardust' + +class ClickableStep extends Component { + state = {} + + handleClick = () => this.setState({ active: !this.state.active }) + + render() { + return + } +} + +const Links = () => ( +
+ + + + + +
+ + + + + + +
+ + + + + +
+) + +export default Links diff --git a/docs/app/Examples/elements/Step/Content/index.js b/docs/app/Examples/elements/Step/Content/index.js new file mode 100644 index 0000000000..6626abedf4 --- /dev/null +++ b/docs/app/Examples/elements/Step/Content/index.js @@ -0,0 +1,27 @@ +import React from 'react' +import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample' +import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection' + +const Content = () => ( + + + + + + + +) + +export default Content diff --git a/docs/app/Examples/elements/Step/Groups/Groups.js b/docs/app/Examples/elements/Step/Groups/Groups.js new file mode 100644 index 0000000000..aa048fb094 --- /dev/null +++ b/docs/app/Examples/elements/Step/Groups/Groups.js @@ -0,0 +1,36 @@ +import React from 'react' +import { Icon, Step } from 'stardust' + +const { Content, Description, Group, Title } = Step +const steps = [ + { icon: 'truck', title: 'Shipping', description: 'Choose your shipping options' }, + { active: true, icon: 'payment', title: 'Billing', description: 'Enter billing information' }, + { disabled: true, icon: 'info', title: 'Confirm Order' }, +] + +const Groups = () => ( +
+ + + + + Shipping + Choose your shipping options + + + + + + + + + + + +
+ + +
+) + +export default Groups diff --git a/docs/app/Examples/elements/Step/Groups/Ordered.js b/docs/app/Examples/elements/Step/Groups/Ordered.js new file mode 100644 index 0000000000..6f6cea92f3 --- /dev/null +++ b/docs/app/Examples/elements/Step/Groups/Ordered.js @@ -0,0 +1,32 @@ +import React from 'react' +import { Step } from 'stardust' + +const { Content, Description, Group, Title } = Step +const steps = [ + { completed: true, title: 'Shipping', description: 'Choose your shipping options' }, + { completed: true, title: 'Billing', description: 'Enter billing information' }, + { active: true, title: 'Confirm Order', description: 'Verify order details' }, +] + +const Ordered = () => ( +
+ + + + Shipping + Choose your shipping options + + + + + + + + +
+ + +
+) + +export default Ordered diff --git a/docs/app/Examples/elements/Step/Groups/Vertical.js b/docs/app/Examples/elements/Step/Groups/Vertical.js new file mode 100644 index 0000000000..57be572006 --- /dev/null +++ b/docs/app/Examples/elements/Step/Groups/Vertical.js @@ -0,0 +1,36 @@ +import React from 'react' +import { Icon, Step } from 'stardust' + +const { Content, Description, Group, Title } = Step +const steps = [ + { completed: true, icon: 'truck', title: 'Shipping', description: 'Choose your shipping options' }, + { completed: true, icon: 'credit card', title: 'Billing', description: 'Enter billing information' }, + { active: true, icon: 'info', title: 'Confirm Order', description: 'Verify order details' }, +] + +const Vertical = () => ( +
+ + + + + Shipping + Choose your shipping options + + + + + + + + + + + +
+ + +
+) + +export default Vertical diff --git a/docs/app/Examples/elements/Step/Groups/index.js b/docs/app/Examples/elements/Step/Groups/index.js new file mode 100644 index 0000000000..e8a45c8f40 --- /dev/null +++ b/docs/app/Examples/elements/Step/Groups/index.js @@ -0,0 +1,36 @@ +import React from 'react' +import { Message } from 'stardust' + +import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample' +import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection' + +// TODO: Update usage after v1 API + +const Groups = () => ( + + + + Steps will automatically stack on mobile. To make steps automatically stack for tablet use the tablet + stackable variation. + + + + + + + +) + +export default Groups diff --git a/docs/app/Examples/elements/Step/States/Active.js b/docs/app/Examples/elements/Step/States/Active.js new file mode 100644 index 0000000000..b7e1092c47 --- /dev/null +++ b/docs/app/Examples/elements/Step/States/Active.js @@ -0,0 +1,20 @@ +import React from 'react' +import { Icon, Step } from 'stardust' + +const { Content, Description, Group, Title } = Step + +const Active = () => ( + + + + + Billing + Enter billing information + + + + + +) + +export default Active diff --git a/docs/app/Examples/elements/Step/States/Completed.js b/docs/app/Examples/elements/Step/States/Completed.js new file mode 100644 index 0000000000..0c70e3b681 --- /dev/null +++ b/docs/app/Examples/elements/Step/States/Completed.js @@ -0,0 +1,20 @@ +import React from 'react' +import { Step } from 'stardust' + +const { Group } = Step + +const Completed = () => ( +
+ + + + +
+ + + + +
+) + +export default Completed diff --git a/docs/app/Examples/elements/Step/States/Disabled.js b/docs/app/Examples/elements/Step/States/Disabled.js new file mode 100644 index 0000000000..5cca943d07 --- /dev/null +++ b/docs/app/Examples/elements/Step/States/Disabled.js @@ -0,0 +1,10 @@ +import React from 'react' +import { Step } from 'stardust' + +const Disabled = () => ( + + Billing + +) + +export default Disabled diff --git a/docs/app/Examples/elements/Step/States/index.js b/docs/app/Examples/elements/Step/States/index.js new file mode 100644 index 0000000000..87b14460cb --- /dev/null +++ b/docs/app/Examples/elements/Step/States/index.js @@ -0,0 +1,29 @@ +import React from 'react' +import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample' +import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection' + +const States = () => ( + + + + + + + + + +) + +export default States diff --git a/docs/app/Examples/elements/Step/Types/Basic.js b/docs/app/Examples/elements/Step/Types/Basic.js new file mode 100644 index 0000000000..73843aa500 --- /dev/null +++ b/docs/app/Examples/elements/Step/Types/Basic.js @@ -0,0 +1,10 @@ +import React from 'react' +import { Step } from 'stardust' + +const Basic = () => ( + + Shipping + +) + +export default Basic diff --git a/docs/app/Examples/elements/Step/Types/index.js b/docs/app/Examples/elements/Step/Types/index.js new file mode 100644 index 0000000000..6dc89e427f --- /dev/null +++ b/docs/app/Examples/elements/Step/Types/index.js @@ -0,0 +1,17 @@ +import React from 'react' +import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample' +import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection' + +const Types = () => ( + + + + + +) + +export default Types diff --git a/docs/app/Examples/elements/Step/Variations/Fluid.js b/docs/app/Examples/elements/Step/Variations/Fluid.js new file mode 100644 index 0000000000..298bf35d20 --- /dev/null +++ b/docs/app/Examples/elements/Step/Variations/Fluid.js @@ -0,0 +1,19 @@ +import React from 'react' +import { Grid, Step } from 'stardust' + +const Fluid = () => ( + + + + + + + + + +

The steps take up the entire column width

+
+
+) + +export default Fluid diff --git a/docs/app/Examples/elements/Step/Variations/Sizes.js b/docs/app/Examples/elements/Step/Variations/Sizes.js new file mode 100644 index 0000000000..2f9983b49f --- /dev/null +++ b/docs/app/Examples/elements/Step/Variations/Sizes.js @@ -0,0 +1,38 @@ +import React from 'react' +import { Step } from 'stardust' + +const steps = [ + { icon: 'truck', title: 'Shipping', description: 'Choose your shipping options' }, + { active: true, icon: 'payment', title: 'Billing', description: 'Enter billing information' }, + { disabled: true, icon: 'info', title: 'Confirm Order', description: 'Verify order details' }, +] +const simpleSteps = [ + { icon: 'truck', title: 'Shipping' }, + { active: true, icon: 'payment', title: 'Billing' }, +] + +const Sizes = () => ( +
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+) + +export default Sizes diff --git a/docs/app/Examples/elements/Step/Variations/Stackable.js b/docs/app/Examples/elements/Step/Variations/Stackable.js new file mode 100644 index 0000000000..c1611b97e1 --- /dev/null +++ b/docs/app/Examples/elements/Step/Variations/Stackable.js @@ -0,0 +1,12 @@ +import React from 'react' +import { Step } from 'stardust' + +const Stackable = () => ( + + + + + +) + +export default Stackable diff --git a/docs/app/Examples/elements/Step/Variations/index.js b/docs/app/Examples/elements/Step/Variations/index.js new file mode 100644 index 0000000000..f9baedac18 --- /dev/null +++ b/docs/app/Examples/elements/Step/Variations/index.js @@ -0,0 +1,27 @@ +import React from 'react' +import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample' +import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection' + +const Variations = () => ( + + + + + + + +) + +export default Variations diff --git a/docs/app/Examples/elements/Step/index.js b/docs/app/Examples/elements/Step/index.js new file mode 100644 index 0000000000..520826a0a0 --- /dev/null +++ b/docs/app/Examples/elements/Step/index.js @@ -0,0 +1,19 @@ +import React from 'react' + +import Content from './Content' +import Groups from './Groups' +import States from './States' +import Types from './Types' +import Variations from './Variations' + +const StepExamples = () => ( +
+ + + + + +
+) + +export default StepExamples diff --git a/src/elements/Step/Step.js b/src/elements/Step/Step.js new file mode 100644 index 0000000000..4510beaec0 --- /dev/null +++ b/src/elements/Step/Step.js @@ -0,0 +1,104 @@ +import cx from 'classnames' +import React, { PropTypes } from 'react' + +import META from '../../utils/Meta' +import { customPropTypes, iconPropRenderer, getUnhandledProps, useKeyOnly } from '../../utils/propUtils' +import StepContent from './StepContent' +import StepDescription from './StepDescription' +import StepGroup from './StepGroup' +import StepTitle from './StepTitle' + +/** A step shows the completion status of an activity in a series of activities. */ +function Step(props) { + const { + active, className, children, completed, description, disabled, icon, href, link, onClick, title, + } = props + const classes = cx( + useKeyOnly(active, 'active'), + useKeyOnly(completed, 'completed'), + useKeyOnly(disabled, 'disabled'), + useKeyOnly(link, 'link'), + className, + 'step', + ) + const rest = getUnhandledProps(Step, props) + + const handleClick = (e) => { + if (onClick) onClick(e) + } + const StepComponent = href || onClick ? 'a' : 'div' + + return ( + + {!children && iconPropRenderer(icon)} + {children || } + + ) +} + +Step._meta = { + name: 'Step', + type: META.type.element, +} + +Step.propTypes = { + /** A step can be highlighted as active. */ + active: PropTypes.bool, + + /** Classes that will be added to the Step className. */ + className: PropTypes.string, + + /** Primary content of the Step. Mutually exclusive with description and title props. */ + children: customPropTypes.all([ + customPropTypes.mutuallyExclusive(['description', 'title']), + PropTypes.node, + ]), + + /** A step can show that a user has completed it. */ + completed: PropTypes.bool, + + /** Shorthand prop for StepDescription. Mutually exclusive with children. */ + description: customPropTypes.all([ + customPropTypes.mutuallyExclusive(['children']), + PropTypes.node, + ]), + + /** Show that the Loader is inactive. */ + disabled: PropTypes.bool, + + /** A step can contain an icon. */ + icon: customPropTypes.all([ + customPropTypes.mutuallyExclusive(['children']), + PropTypes.node, + ]), + + /** A step can be link. */ + link: PropTypes.bool, + + /** Render as an `a` tag instead of a `div` and adds the href attribute. */ + href: PropTypes.string, + + /** Render as an `a` tag instead of a `div` and called with event on Step click. */ + onClick: PropTypes.func, + + /** A step can show a ordered sequence of steps. Passed from StepGroup. */ + ordered: PropTypes.bool, + + /** Shorthand prop for StepTitle. Mutually exclusive with children. */ + title: customPropTypes.all([ + customPropTypes.mutuallyExclusive(['children']), + PropTypes.node, + ]), +} + +Step.Content = StepContent +Step.Description = StepDescription +Step.Group = StepGroup +Step.Title = StepTitle + +export default Step diff --git a/src/elements/Step/StepContent.js b/src/elements/Step/StepContent.js new file mode 100644 index 0000000000..776d481ca7 --- /dev/null +++ b/src/elements/Step/StepContent.js @@ -0,0 +1,55 @@ +import React, { PropTypes } from 'react' +import cx from 'classnames' + +import META from '../../utils/Meta' +import { customPropTypes, getUnhandledProps } from '../../utils/propUtils' +import StepDescription from './StepDescription' +import StepTitle from './StepTitle' + +function StepContent(props) { + const { className, children, description, title } = props + const classes = cx(className, 'content') + const rest = getUnhandledProps(StepContent, props) + + if (children) { + return
{ children }
+ } + + return ( +
+ { title && } + { description && } +
+ ) +} + +StepContent._meta = { + name: 'StepContent', + parent: 'Step', + type: META.type.element, +} + +StepContent.propTypes = { + /** Classes that will be added to the StepContent className. */ + className: PropTypes.string, + + /** Primary content of StepContent. Mutually exclusive with description and title props. */ + children: customPropTypes.all([ + customPropTypes.mutuallyExclusive(['description', 'title']), + PropTypes.node, + ]), + + /** Primary content of the StepDescription. Mutually exclusive with children. */ + description: customPropTypes.all([ + customPropTypes.mutuallyExclusive(['children']), + PropTypes.node, + ]), + + /** Primary content of the StepTitle. Mutually exclusive with children. */ + title: customPropTypes.all([ + customPropTypes.mutuallyExclusive(['children']), + PropTypes.node, + ]), +} + +export default StepContent diff --git a/src/elements/Step/StepDescription.js b/src/elements/Step/StepDescription.js new file mode 100644 index 0000000000..a8403de00b --- /dev/null +++ b/src/elements/Step/StepDescription.js @@ -0,0 +1,38 @@ +import cx from 'classnames' +import React, { PropTypes } from 'react' + +import META from '../../utils/Meta' +import { customPropTypes, getUnhandledProps } from '../../utils/propUtils' + +function StepDescription(props) { + const { className, children, description } = props + const classes = cx(className, 'description') + const rest = getUnhandledProps(StepDescription, props) + + return
{ children || description }
+} + +StepDescription._meta = { + name: 'StepDescription', + parent: 'Step', + type: META.type.element, +} + +StepDescription.propTypes = { + /** Classes that will be added to the StepDescription className. */ + className: PropTypes.string, + + /** Primary content of the StepDescription. Mutually exclusive with description prop. */ + children: customPropTypes.all([ + customPropTypes.mutuallyExclusive(['description']), + PropTypes.node, + ]), + + /** Primary content of the StepDescription. Mutually exclusive with children. */ + description: customPropTypes.all([ + customPropTypes.mutuallyExclusive(['children']), + PropTypes.node, + ]), +} + +export default StepDescription diff --git a/src/elements/Step/StepGroup.js b/src/elements/Step/StepGroup.js new file mode 100644 index 0000000000..431ea3990d --- /dev/null +++ b/src/elements/Step/StepGroup.js @@ -0,0 +1,80 @@ +import _ from 'lodash' +import cx from 'classnames' +import React, { PropTypes } from 'react' + +import META from '../../utils/Meta' +import { customPropTypes, getUnhandledProps, useValueAndKey, useKeyOnly } from '../../utils/propUtils' +import * as sui from '../../utils/semanticUtils' +import Step from './Step' + +function StepGroup(props) { + const { className, children, fluid, items, ordered, size, stackable, vertical } = props + const classes = cx( + 'ui', + useKeyOnly(fluid, 'fluid'), + useKeyOnly(ordered, 'ordered'), + useValueAndKey(stackable, 'stackable'), + useKeyOnly(vertical, 'vertical'), + size, + className, + 'steps', + ) + const rest = getUnhandledProps(StepGroup, props) + + const content = items + ? items.map(item => { + const key = item.key || [item.title, item.description].join('-') + return + }) : children + + return
{content}
+} + +StepGroup._meta = { + name: 'StepGroup', + parent: 'Step', + props: { + sizes: _.without(sui.sizes, 'medium'), + stackable: ['tablet'], + }, + type: META.type.element, +} + +StepGroup.propTypes = { + /** Classes that will be added to the StepGroup className. */ + className: PropTypes.string, + + /** Primary content of the StepGroup. Mutually exclusive with items prop. */ + children: customPropTypes.all([ + customPropTypes.mutuallyExclusive(['items']), + PropTypes.node, + ]), + + /** A fluid step takes up the width of its container. */ + fluid: PropTypes.bool, + + /** Primary content of the StepGroup. Mutually exclusive with items children. */ + items: customPropTypes.all([ + customPropTypes.mutuallyExclusive(['description', 'title']), + PropTypes.arrayOf(PropTypes.shape({ + description: PropTypes.node, + icon: PropTypes.node, + key: PropTypes.string, + title: PropTypes.node, + })), + ]), + + /** A step can show a ordered sequence of steps. */ + ordered: PropTypes.bool, + + /** Steps can have different sizes. */ + size: PropTypes.oneOf(StepGroup._meta.props.sizes), + + /** A step can stack vertically only on smaller screens. */ + stackable: PropTypes.oneOf(StepGroup._meta.props.stackable), + + /** A step can be displayed stacked vertically. */ + vertical: PropTypes.bool, +} + +export default StepGroup diff --git a/src/elements/Step/StepTitle.js b/src/elements/Step/StepTitle.js new file mode 100644 index 0000000000..aedcb45df1 --- /dev/null +++ b/src/elements/Step/StepTitle.js @@ -0,0 +1,38 @@ +import cx from 'classnames' +import React, { PropTypes } from 'react' + +import META from '../../utils/Meta' +import { customPropTypes, getUnhandledProps } from '../../utils/propUtils' + +function StepTitle(props) { + const { className, children, title } = props + const classes = cx(className, 'title') + const rest = getUnhandledProps(StepTitle, props) + + return
{ children || title }
+} + +StepTitle._meta = { + name: 'StepTitle', + parent: 'Step', + type: META.type.element, +} + +StepTitle.propTypes = { + /** Classes that will be added to the StepTitle className. */ + className: PropTypes.string, + + /** Primary content of the StepTitle. Mutually exclusive with title prop. */ + children: customPropTypes.all([ + customPropTypes.mutuallyExclusive(['title']), + PropTypes.node, + ]), + + /** Primary content of the StepTitle. Mutually exclusive with children. */ + title: customPropTypes.all([ + customPropTypes.mutuallyExclusive(['children']), + PropTypes.node, + ]), +} + +export default StepTitle diff --git a/src/index.js b/src/index.js index 2e8c46287a..3c6bfa8569 100644 --- a/src/index.js +++ b/src/index.js @@ -55,6 +55,7 @@ export const ListItem = deprecateComponent('ListItem', 'Use "List.Item" instead. export Segment from './elements/Segment/Segment' export Segments from './elements/Segment/SegmentSegments' +export Step from './elements/Step/Step' export Rail from './elements/Rail/Rail' // ---------------------------------------- diff --git a/test/specs/elements/Step/Step-test.js b/test/specs/elements/Step/Step-test.js new file mode 100644 index 0000000000..1eca600a32 --- /dev/null +++ b/test/specs/elements/Step/Step-test.js @@ -0,0 +1,71 @@ +import faker from 'faker' +import React from 'react' + +import * as common from 'test/specs/commonTests' +import sandbox from 'test/utils/Sandbox-util' +import Step from 'src/elements/Step/Step' +import StepContent from 'src/elements/Step/StepContent' +import StepDescription from 'src/elements/Step/StepDescription' +import StepTitle from 'src/elements/Step/StepTitle' + +describe('Step', () => { + common.isConformant(Step) + common.implementsIconProp(Step) + common.hasSubComponents(Step, [StepContent, StepDescription, StepTitle]) + common.propKeyOnlyToClassName(Step, 'active') + common.propKeyOnlyToClassName(Step, 'completed') + common.propKeyOnlyToClassName(Step, 'disabled') + common.propKeyOnlyToClassName(Step, 'link') + common.rendersChildren(Step) + + it('renders only children by default', () => { + shallow({faker.hacker.phrase()}) + .should.not.have.descendants('StepContent') + }) + + it('renders Description component', () => { + const wrapper = mount() + + wrapper.should.have.descendants('StepContent') + wrapper.find('StepContent').should.have.descendants('StepDescription') + }) + + it('renders Title component', () => { + const wrapper = mount() + + wrapper.should.have.descendants('StepContent') + wrapper.find('StepContent').should.have.descendants('StepTitle') + }) + + describe('renders different elements', () => { + it('
by default', () => { + shallow().should.have.tagName('div') + }) + + it(' with `href` prop', () => { + const url = faker.internet.url() + const wrapper = shallow() + + wrapper.should.have.tagName('a') + wrapper.should.have.attr('href', url) + }) + + describe('onClick prop', () => { + it('can be omitted', () => { + const click = () => mount({faker.hacker.phrase()}).simulate('click') + expect(click).to.not.throw() + }) + + it('renders and handles click', () => { + const handleClick = sandbox.spy() + const wrapper = mount() + + wrapper.should.have.tagName('a') + wrapper.simulate('click') + + handleClick.should.have.been.calledOnce() + handleClick.should.have.been.calledWithMatch({}) + }) + }) + }) +}) diff --git a/test/specs/elements/Step/StepContent-test.js b/test/specs/elements/Step/StepContent-test.js new file mode 100644 index 0000000000..b1448c3906 --- /dev/null +++ b/test/specs/elements/Step/StepContent-test.js @@ -0,0 +1,30 @@ +import faker from 'faker' +import React from 'react' + +import * as common from 'test/specs/commonTests' +import StepContent from 'src/elements/Step/StepContent' +import StepDescription from 'src/elements/Step/StepDescription' +import StepTitle from 'src/elements/Step/StepTitle' + +describe('StepContent', () => { + common.isConformant(StepContent) + common.rendersChildren(StepContent) + + describe('description prop', () => { + it('renders description component', () => { + const text = faker.hacker.phrase() + + shallow() + .should.contain() + }) + }) + + describe('title prop', () => { + it('renders title component', () => { + const text = faker.hacker.phrase() + + shallow() + .should.contain() + }) + }) +}) diff --git a/test/specs/elements/Step/StepDescription-test.js b/test/specs/elements/Step/StepDescription-test.js new file mode 100644 index 0000000000..7ade4c1d5f --- /dev/null +++ b/test/specs/elements/Step/StepDescription-test.js @@ -0,0 +1,26 @@ +import faker from 'faker' +import React from 'react' + +import * as common from 'test/specs/commonTests' +import StepDescription from 'src/elements/Step/StepDescription' + +describe('StepDescription', () => { + common.isConformant(StepDescription) + common.rendersChildren(StepDescription) + + describe('description prop', () => { + it('renders child text', () => { + const text = faker.hacker.phrase() + + shallow() + .should.contain.text(text) + }) + + it('renders child node', () => { + const child =
+ + shallow() + .should.contain(child) + }) + }) +}) diff --git a/test/specs/elements/Step/StepGroup-test.js b/test/specs/elements/Step/StepGroup-test.js new file mode 100644 index 0000000000..2779ca7a44 --- /dev/null +++ b/test/specs/elements/Step/StepGroup-test.js @@ -0,0 +1,44 @@ +import faker from 'faker' +import React from 'react' +import * as common from 'test/specs/commonTests' +import Step from 'src/elements/Step/Step' +import StepGroup from 'src/elements/Step/StepGroup' + +describe('StepGroup', () => { + common.isConformant(StepGroup) + common.hasUIClassName(StepGroup) + common.propKeyOnlyToClassName(StepGroup, 'fluid') + common.propKeyOnlyToClassName(StepGroup, 'ordered') + common.propKeyAndValueToClassName(StepGroup, 'stackable') + common.propKeyOnlyToClassName(StepGroup, 'vertical') + + describe('renders children', () => { + const firstText = faker.hacker.phrase() + const secondText = faker.hacker.phrase() + + it('with `children` prop', () => { + const wrapper = mount( + + {firstText} + {secondText} + + ) + .find('Step') + + wrapper.first().should.contain.text(firstText) + wrapper.last().should.contain.text(secondText) + }) + + it('with `items` prop', () => { + const items = [ + { title: firstText }, + { title: secondText }, + ] + + const wrapper = mount().find('Step') + + wrapper.first().find('StepTitle').should.contain.text(firstText) + wrapper.last().find('StepTitle').should.contain.text(secondText) + }) + }) +}) diff --git a/test/specs/elements/Step/StepTitle-test.js b/test/specs/elements/Step/StepTitle-test.js new file mode 100644 index 0000000000..18d5887099 --- /dev/null +++ b/test/specs/elements/Step/StepTitle-test.js @@ -0,0 +1,26 @@ +import faker from 'faker' +import React from 'react' + +import * as common from 'test/specs/commonTests' +import StepTitle from 'src/elements/Step/StepTitle' + +describe('StepTitle', () => { + common.isConformant(StepTitle) + common.rendersChildren(StepTitle) + + describe('description prop', () => { + it('renders child text', () => { + const text = faker.hacker.phrase() + + shallow() + .should.contain.text(text) + }) + + it('renders child node', () => { + const child =
+ + shallow() + .should.contain(child) + }) + }) +})