Skip to content

Commit

Permalink
refactor(tab): move to completely generic tab
Browse files Browse the repository at this point in the history
  • Loading branch information
levithomason committed May 22, 2017
1 parent 20c40c8 commit 39c928b
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 278 deletions.
12 changes: 7 additions & 5 deletions docs/app/Examples/modules/Tab/Types/TabExampleBasic.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React from 'react'
import { Tab } from 'semantic-ui-react'

const panes = [
{ menuItem: 'Tab 1', render: () => <Tab.Pane>Tab 1 Content</Tab.Pane> },
{ menuItem: 'Tab 2', render: () => <Tab.Pane>Tab 2 Content</Tab.Pane> },
{ menuItem: 'Tab 3', render: () => <Tab.Pane>Tab 3 Content</Tab.Pane> },
]

const TabExampleBasic = () => (
<Tab>
<Tab.Pane menuItem='Tab 1'>Tab 1 Content</Tab.Pane>
<Tab.Pane menuItem='Tab 2'>Tab 2 Content</Tab.Pane>
<Tab.Pane menuItem='Tab 3'>Tab 3 Content</Tab.Pane>
</Tab>
<Tab panes={panes} />
)

export default TabExampleBasic
12 changes: 7 additions & 5 deletions docs/app/Examples/modules/Tab/Types/TabExamplePointing.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React from 'react'
import { Tab } from 'semantic-ui-react'

const panes = [
{ menuItem: 'Tab 1', render: () => <Tab.Pane attached={false}>Tab 1 Content</Tab.Pane> },
{ menuItem: 'Tab 2', render: () => <Tab.Pane attached={false}>Tab 2 Content</Tab.Pane> },
{ menuItem: 'Tab 3', render: () => <Tab.Pane attached={false}>Tab 3 Content</Tab.Pane> },
]

const TabExamplePointing = () => (
<Tab pointing>
<Tab.Pane menuItem='Tab 1'>Tab 1 Content</Tab.Pane>
<Tab.Pane menuItem='Tab 2'>Tab 2 Content</Tab.Pane>
<Tab.Pane menuItem='Tab 3'>Tab 3 Content</Tab.Pane>
</Tab>
<Tab menu={{ pointing: true }} panes={panes} />
)

export default TabExamplePointing
12 changes: 7 additions & 5 deletions docs/app/Examples/modules/Tab/Types/TabExampleSecondary.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React from 'react'
import { Tab } from 'semantic-ui-react'

const panes = [
{ menuItem: 'Tab 1', render: () => <Tab.Pane attached={false}>Tab 1 Content</Tab.Pane> },
{ menuItem: 'Tab 2', render: () => <Tab.Pane attached={false}>Tab 2 Content</Tab.Pane> },
{ menuItem: 'Tab 3', render: () => <Tab.Pane attached={false}>Tab 3 Content</Tab.Pane> },
]

const TabExampleSecondary = () => (
<Tab secondary>
<Tab.Pane menuItem='Tab 1'>Tab 1 Content</Tab.Pane>
<Tab.Pane menuItem='Tab 2'>Tab 2 Content</Tab.Pane>
<Tab.Pane menuItem='Tab 3'>Tab 3 Content</Tab.Pane>
</Tab>
<Tab menu={{ secondary: true }} panes={panes} />
)

export default TabExampleSecondary
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React from 'react'
import { Tab } from 'semantic-ui-react'

const panes = [
{ menuItem: 'Tab 1', render: () => <Tab.Pane attached={false}>Tab 1 Content</Tab.Pane> },
{ menuItem: 'Tab 2', render: () => <Tab.Pane attached={false}>Tab 2 Content</Tab.Pane> },
{ menuItem: 'Tab 3', render: () => <Tab.Pane attached={false}>Tab 3 Content</Tab.Pane> },
]

const TabExampleSecondaryPointing = () => (
<Tab secondary pointing>
<Tab.Pane menuItem='Tab 1'>Tab 1 Content</Tab.Pane>
<Tab.Pane menuItem='Tab 2'>Tab 2 Content</Tab.Pane>
<Tab.Pane menuItem='Tab 3'>Tab 3 Content</Tab.Pane>
</Tab>
<Tab menu={{ secondary: true, pointing: true }} panes={panes} />
)

export default TabExampleSecondaryPointing
12 changes: 7 additions & 5 deletions docs/app/Examples/modules/Tab/Types/TabExampleText.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React from 'react'
import { Tab } from 'semantic-ui-react'

const panes = [
{ menuItem: 'Tab 1', render: () => <Tab.Pane attached={false}>Tab 1 Content</Tab.Pane> },
{ menuItem: 'Tab 2', render: () => <Tab.Pane attached={false}>Tab 2 Content</Tab.Pane> },
{ menuItem: 'Tab 3', render: () => <Tab.Pane attached={false}>Tab 3 Content</Tab.Pane> },
]

const TabExampleText = () => (
<Tab text>
<Tab.Pane menuItem='Tab 1'>Tab 1 Content</Tab.Pane>
<Tab.Pane menuItem='Tab 2'>Tab 2 Content</Tab.Pane>
<Tab.Pane menuItem='Tab 3'>Tab 3 Content</Tab.Pane>
</Tab>
<Tab menu={{ text: true }} panes={panes} />
)

export default TabExampleText
6 changes: 3 additions & 3 deletions docs/app/Examples/modules/Tab/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import MenuVariations from './MenuVariations'
const TabExamples = () => (
<div>
<Types />
<States />
<MenuVariations />
<Usage />
{/*<States />*/}
{/*<MenuVariations />*/}
{/*<Usage />*/}
</div>
)

Expand Down
3 changes: 3 additions & 0 deletions src/collections/Menu/Menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import React from 'react'
import {
AutoControlledComponent as Component,
customPropTypes,
createShorthandFactory,
getElementType,
getUnhandledProps,
META,
Expand Down Expand Up @@ -215,4 +216,6 @@ class Menu extends Component {
}
}

Menu.create = createShorthandFactory(Menu, items => ({ items }))

export default Menu
157 changes: 28 additions & 129 deletions src/modules/Tab/Tab.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import _ from 'lodash/fp'
import React, { Children, PropTypes } from 'react'
import React from 'react'
import PropTypes from 'prop-types'

import {
AutoControlledComponent as Component,
Expand Down Expand Up @@ -27,9 +28,6 @@ class Tab extends Component {
/** An element type to render as (string or function). */
as: customPropTypes.as,

/** One or more Tab.Pane components. */
children: PropTypes.node,

/** The initial activeIndex. */
defaultActiveIndex: PropTypes.number,

Expand All @@ -49,160 +47,61 @@ class Tab extends Component {
*/
onChange: PropTypes.func,

// ----------------------------------------
// Menu Props

/** The Tab menu may appear attached to the Tab.Panes. */
attached: Menu.propTypes.attached,

/** A menu item or menu can have no borders. */
borderless: Menu.propTypes.borderless,

/** Additional colors can be specified. */
color: Menu.propTypes.color,

/** A menu may have just icons (bool) or labeled icons. */
icon: Menu.propTypes.icon,

/** A menu may have its colors inverted to show greater contrast. */
inverted: Menu.propTypes.inverted,

/** A menu can point to show its relationship to nearby content. */
pointing: Menu.propTypes.pointing,

/** A menu can adjust its appearance to de-emphasize its contents. */
secondary: Menu.propTypes.secondary,

/** A menu can vary in size. */
size: Menu.propTypes.size,

/** A menu can be formatted to show tabs of information. */
tabular: Menu.propTypes.tabular,

/** A menu can be formatted for text content. */
text: Menu.propTypes.text,
}
/** Shorthand props for the Menu. */
menu: customPropTypes.contentShorthand,

static defaultProps = {
attached: true,
tabular: true,
/** Shorthand props for the Menu. */
panes: PropTypes.arrayOf(PropTypes.shape({
menuItem: PropTypes.string.isRequired,
render: PropTypes.func.isRequired,
})),
}

static autoControlledProps = [
'activeIndex',
]

static defaultProps = {
menu: { attached: true, tabular: true },
}

static Pane = TabPane

state = {
activeIndex: 0,
}

componentWillMount() {
if (super.componentWillMount) super.componentWillMount()

this.parsePanes(this.props)
}

componentWillReceiveProps(nextProps) {
if (super.componentWillReceiveProps) super.componentWillReceiveProps(nextProps)

this.parsePanes(nextProps)
}

handleItemClick = (e, { index }) => {
_.invoke('onChange', this.props, e, { activeIndex: index, ...this.props })
this.trySetState({ activeIndex: index })
_.invoke('onChange', this.props, [e, { activeIndex: index, panes: this.panes[index] }])
}

parsePanes = (props) => {
const { children } = props

this.menuItems = []
this.panes = []

Children.forEach(children, pane => {
if (pane.type !== TabPane) return

const { menuItem, ...panes } = pane.props
this.menuItems.push(menuItem)
this.panes.push(panes)
})
}

shouldMenuAttach = () => {
const { pointing, secondary, text } = this.props

return !secondary && !pointing && !text
}

renderMenu() {
const {
attached,
borderless,
color,
icon,
inverted,
pointing,
secondary,
size,
tabular,
text,
} = this.props
const { menu, panes } = this.props
const { activeIndex } = this.state

const shouldAttach = this.shouldMenuAttach()

return (
<Menu
attached={shouldAttach && attached}
borderless={borderless}
color={color}
icon={icon}
inverted={inverted}
pointing={pointing}
secondary={secondary}
size={size}
tabular={shouldAttach && tabular}
text={text}
items={this.menuItems}
onItemClick={this.handleItemClick}
activeIndex={activeIndex}
/>
)
}

renderPanes() {
const { attached } = this.props
const { activeIndex } = this.state

const pane = this.panes[activeIndex]

const defaultProps = {
active: !_.isNil(pane.active) ? pane.active : true,
}

// attach segment to opposite side of menu
// check for `true` last, it is the default value so likely to be present
// otherwise, the calculated attached is always 'bottom' unless `false` is passed
if (!this.shouldMenuAttach()) defaultProps.attached = false
else if (attached === 'bottom') defaultProps.attached = 'top'
else if (attached === true || attached === 'top') defaultProps.attached = 'bottom'

return TabPane.create(pane, defaultProps)
return Menu.create(menu, {
overrideProps: {
items: _.map('menuItem', panes),
onItemClick: this.handleItemClick,
activeIndex,
},
})
}

render() {
const { attached } = this.props
const { panes } = this.props
const { activeIndex } = this.state

const menu = this.renderMenu()
const rest = getUnhandledProps(Tab, this.props)
const ElementType = getElementType(Tab, this.props)

return (
<ElementType {...rest}>
{attached !== 'bottom' && this.renderMenu()}
{this.renderPanes()}
{attached === 'bottom' && this.renderMenu()}
{menu.props.attached !== 'bottom' && menu}
{_.invoke('render', panes[activeIndex], this.props)}
{menu.props.attached === 'bottom' && menu}
</ElementType>
)
}
Expand Down
Loading

0 comments on commit 39c928b

Please sign in to comment.