Skip to content

Commit

Permalink
feat(Tab): add renderActiveOnly prop (#1976)
Browse files Browse the repository at this point in the history
* feat(Tab): add `renderActiveOnly` prop

* style(Tab): fix lint

* docs(Tab): update verbiage
  • Loading branch information
layershifter authored and levithomason committed Aug 27, 2017
1 parent 1980806 commit e3c4972
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 20 deletions.
14 changes: 14 additions & 0 deletions docs/app/Examples/modules/Tab/Types/TabExampleBasicAll.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react'
import { Tab } from 'semantic-ui-react'

const panes = [
{ menuItem: 'Tab 1', pane: 'Tab 1 Content' },
{ menuItem: 'Tab 2', pane: 'Tab 2 Content' },
{ menuItem: 'Tab 3', pane: 'Tab 3 Content' },
]

const TabExampleBasicAll = () => (
<Tab panes={panes} renderActiveOnly={false} />
)

export default TabExampleBasicAll
24 changes: 23 additions & 1 deletion docs/app/Examples/modules/Tab/Types/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,36 @@
import React from 'react'
import { Message } from 'semantic-ui-react'

import ComponentExample from 'docs/app/Components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/app/Components/ComponentDoc/ExampleSection'

const TabTypesExamples = () => (
<ExampleSection title='Types'>
<Message info>
<Message.Header>
Controlling <code>Tab</code> pane re-renders
</Message.Header>
<Message.List>
<Message.Item>
<code>renderActiveOnly</code> (default) Only the active pane is rendered.
Switching tabs unmounts the current pane and mounts the new pane.
</Message.Item>
<Message.Item>
<code>renderActiveOnly={'{false}'}</code> All panes are rendered on Tab mount.
Switching tabs hides the current pane and shows the new pane, without unmounting panes.
</Message.Item>
</Message.List>
</Message>

<ComponentExample
title='Basic'
description='A basic tab'
description='A basic tab.'
examplePath='modules/Tab/Types/TabExampleBasic'
/>
<ComponentExample
description={<span>A basic tab using <code>renderActiveOnly={'{false}'}</code>.</span>}
examplePath='modules/Tab/Types/TabExampleBasicAll'
/>
<ComponentExample
title='Pointing Menu'
description='A tab menu can point to its tab panes.'
Expand Down
26 changes: 26 additions & 0 deletions docs/app/Examples/modules/Tab/Usage/TabExamplePaneShorthand.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react'
import { List, Label, Tab } from 'semantic-ui-react'

const panes = [
{ menuItem: 'Tab 1', pane: { key: 'tab1', content: 'This is massive tab', size: 'massive' } },
{ menuItem: 'Tab 2', pane: { key: 'tab2', content: 'This tab has a center aligned text', textAlign: 'center' } },
{ menuItem: 'Tab 3', pane: { key: 'tab3', content: <div>This tab contains an <Label>JSX</Label> element</div> } },
{ menuItem: 'Tab 4',
pane: (
<Tab.Pane key='tab4'>
<p>This tab has a complex content</p>

<List>
<List.Item>Apples</List.Item>
<List.Item>Pears</List.Item>
<List.Item>Oranges</List.Item>
</List>
</Tab.Pane>
) },
]

const TabExampleContentShorthand = () => (
<Tab panes={panes} renderActiveOnly={false} />
)

export default TabExampleContentShorthand
9 changes: 9 additions & 0 deletions docs/app/Examples/modules/Tab/Usage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ const TabUsageExamples = () => (
description='You can pass any shorthand value as a menu item.'
examplePath='modules/Tab/Usage/TabExampleCustomMenuItem'
/>
<ComponentExample
title='Pane Shorthands'
description={(
<span>
You can use an item shorthands when you're using <code>renderActiveOnly={'{false}'}</code>.
</span>
)}
examplePath='modules/Tab/Usage/TabExamplePaneShorthand'
/>
</ExampleSection>
)

Expand Down
25 changes: 21 additions & 4 deletions src/modules/Tab/Tab.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as React from 'react';
import { default as TabPane } from './TabPane';

import { SemanticShorthandItem} from '../..';
import { default as TabPane, TabPaneProps } from './TabPane';

export interface TabProps {
[key: string]: any;
Expand Down Expand Up @@ -29,11 +31,26 @@ export interface TabProps {
*/
onTabChange?: (event: React.MouseEvent<HTMLDivElement>, data: TabProps) => void;

/** Shorthand props for the Menu. */
/**
* Array of objects describing each Menu.Item and Tab.Pane:
* {
* menuItem: 'Home',
* render: () => <Tab.Pane>Welcome!</Tab.Pane>,
* }
* or
* {
* menuItem: 'Home',
* pane: 'Welcome',
* }
*/
panes?: Array<{
menuItem: any;
render: () => React.ReactNode;
content?: SemanticShorthandItem<TabPaneProps>;
menuItem?: any;
render?: () => React.ReactNode;
}>;

/** A Tab can render only active pane. */
renderActiveOnly?: boolean;
}

interface TabComponent extends React.ComponentClass<TabProps> {
Expand Down
31 changes: 20 additions & 11 deletions src/modules/Tab/Tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,24 +53,28 @@ class Tab extends Component {

/**
* Array of objects describing each Menu.Item and Tab.Pane:
* {
* menuItem: 'Home',
* render: () => <Tab.Pane>Welcome!</Tab.Pane>
* }
* { menuItem: 'Home', render: () => <Tab.Pane /> }
* or
* { menuItem: 'Home', pane: 'Welcome' }
*/
panes: PropTypes.arrayOf(PropTypes.shape({
menuItem: customPropTypes.itemShorthand,
render: PropTypes.func.isRequired,
pane: customPropTypes.itemShorthand,
render: PropTypes.func,
})),

/** A Tab can render only active pane. */
renderActiveOnly: PropTypes.bool,
}

static autoControlledProps = [
'activeIndex',
]

static defaultProps = {
menu: { attached: true, tabular: true },
grid: { paneWidth: 12, tabWidth: 4 },
menu: { attached: true, tabular: true },
renderActiveOnly: true,
}

static _meta = {
Expand All @@ -89,11 +93,16 @@ class Tab extends Component {
this.trySetState({ activeIndex: index })
}

renderActivePane() {
const { panes } = this.props
renderItems() {
const { panes, renderActiveOnly } = this.props
const { activeIndex } = this.state

return _.invoke(_.get(panes, `[${activeIndex}]`), 'render', this.props)
if (renderActiveOnly) return _.invoke(_.get(panes, `[${activeIndex}]`), 'render', this.props)
return _.map(panes, ({ pane }, index) => TabPane.create(pane, {
overrideProps: {
active: index === activeIndex,
},
}))
}

renderMenu() {
Expand All @@ -118,7 +127,7 @@ class Tab extends Component {
{menu.props.tabular !== 'right' && GridColumn.create({ width: tabWidth, children: menu })}
{GridColumn.create({
width: paneWidth,
children: this.renderActivePane(),
children: this.renderItems(),
stretched: true,
})}
{menu.props.tabular === 'right' && GridColumn.create({ width: tabWidth, children: menu })}
Expand All @@ -138,7 +147,7 @@ class Tab extends Component {
return (
<ElementType {...rest}>
{menu.props.attached !== 'bottom' && menu}
{this.renderActivePane()}
{this.renderItems()}
{menu.props.attached === 'bottom' && menu}
</ElementType>
)
Expand Down
7 changes: 7 additions & 0 deletions src/modules/Tab/TabPane.d.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import * as React from 'react';
import { SemanticShorthandContent } from '../..';

export interface TabPaneProps {
[key: string]: any;

/** An element type to render as (string or function). */
as?: any;

/** A tab pane can be active. */
active?: boolean;

/** Primary content. */
children?: React.ReactNode;

/** Additional classes. */
className?: string;

/** Shorthand for primary content. */
content?: SemanticShorthandContent;

/** A Tab.Pane can display a loading indicator. */
loading?: boolean;
}
Expand Down
17 changes: 13 additions & 4 deletions src/modules/Tab/TabPane.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import React from 'react'

import {
childrenUtils,
createShorthandFactory,
customPropTypes,
getElementType,
Expand All @@ -16,11 +17,12 @@ import Segment from '../../elements/Segment/Segment'
* A tab pane holds the content of a tab.
*/
function TabPane(props) {
const { children, className, loading } = props
const { active, children, className, content, loading } = props

const classes = cx(
useKeyOnly(active, 'active'),
useKeyOnly(loading, 'loading'),
'active tab',
'tab',
className,
)
const rest = getUnhandledProps(TabPane, props)
Expand All @@ -33,7 +35,7 @@ function TabPane(props) {

return (
<ElementType {...calculatedDefaultProps} {...rest} className={classes}>
{children}
{childrenUtils.isNil(children) ? content : children}
</ElementType>
)
}
Expand All @@ -46,22 +48,29 @@ TabPane._meta = {

TabPane.defaultProps = {
as: Segment,
active: true,
}

TabPane.propTypes = {
/** An element type to render as (string or function). */
as: customPropTypes.as,

/** A tab pane can be active. */
active: PropTypes.bool,

/** Primary content. */
children: PropTypes.node,

/** Additional classes. */
className: PropTypes.string,

/** Shorthand for primary content. */
content: customPropTypes.contentShorthand,

/** A Tab.Pane can display a loading indicator. */
loading: PropTypes.bool,
}

TabPane.create = createShorthandFactory(TabPane, children => ({ children }))
TabPane.create = createShorthandFactory(TabPane, content => ({ content }))

export default TabPane
16 changes: 16 additions & 0 deletions test/specs/modules/Tab/Tab-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,20 @@ describe('Tab', () => {
spy.lastCall.args[1].should.have.property('activeIndex', 2)
})
})

describe('renderActiveOnly', () => {
it('renders all tabs when false', () => {
const textPanes = [
{ pane: 'Tab 1' },
{ pane: 'Tab 2' },
{ pane: 'Tab 3' },
]
const items = mount(<Tab panes={textPanes} renderActiveOnly={false} />).find('TabPane')

items.should.have.lengthOf(3)
items.at(0).should.contain.text('Tab 1')
items.at(1).should.contain.text('Tab 2')
items.at(2).should.contain.text('Tab 3')
})
})
})
5 changes: 5 additions & 0 deletions test/specs/modules/Tab/TabPane-test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import React from 'react'

import TabPane from 'src/modules/Tab/TabPane'
import * as common from 'test/specs/commonTests'

describe('TabPane', () => {
common.isConformant(TabPane)

common.implementsCreateMethod(TabPane)

common.propKeyOnlyToClassName(TabPane, 'active')
common.propKeyOnlyToClassName(TabPane, 'loading')

it('renders a Segment by default', () => {
Expand Down

0 comments on commit e3c4972

Please sign in to comment.