Skip to content

Commit

Permalink
feat(Tab): add renderActiveOnly prop
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander Fedyashov committed Aug 17, 2017
1 parent a4780cd commit a2b4a24
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 14 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
23 changes: 22 additions & 1 deletion docs/app/Examples/modules/Tab/Types/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@
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>
<code>Tab</code> component implements two different behaviours
</Message.Header>
<Message.List>
<Message.Item>
<code>renderActiveOnly={'{true}'}</code> - default behaviour, in this case only active pane will be rendered.
Can cause problems with <code>state</code> of inactive panes.
</Message.Item>
<Message.Item>
<code>renderActiveOnly={'{false}'}</code> - all panes will be rendered with all their children.
</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
25 changes: 25 additions & 0 deletions docs/app/Examples/modules/Tab/Usage/TabExamplePaneShorthand.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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
23 changes: 20 additions & 3 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 All @@ -26,11 +28,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<{
content?: SemanticShorthandItem<TabPaneProps>;
menuItem: any;
render: () => React.ReactNode;
render?: () => React.ReactNode;
}>;

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

interface TabComponent extends React.ComponentClass<TabProps> {
Expand Down
31 changes: 25 additions & 6 deletions src/modules/Tab/Tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,22 @@ class Tab extends Component {
* Array of objects describing each Menu.Item and Tab.Pane:
* {
* menuItem: 'Home',
* render: () => <Tab.Pane>Welcome!</Tab.Pane>
* render: () => <Tab.Pane>Welcome!</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 = [
Expand All @@ -65,6 +74,7 @@ class Tab extends Component {

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

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

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

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

renderMenu() {
const { menu, panes } = this.props
const { activeIndex } = this.state
Expand All @@ -97,17 +119,14 @@ class Tab extends Component {
}

render() {
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}>
{menu.props.attached !== 'bottom' && menu}
{_.invoke(_.get(panes, `[${activeIndex}]`), 'render', this.props)}
{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 @@ -147,4 +147,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 a2b4a24

Please sign in to comment.