Skip to content
This repository was archived by the owner on Jan 20, 2022. It is now read-only.

Commit 11823be

Browse files
authored
Accordion component (#36)
* adding the accordion component and one example of Accordion with nested list
1 parent ee3e8eb commit 11823be

File tree

20 files changed

+681
-4
lines changed

20 files changed

+681
-4
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React from 'react'
2+
import { Accordion } from 'stardust'
3+
import faker from 'faker'
4+
5+
const panels = [
6+
{
7+
title: 'What is a point?',
8+
content: faker.hacker.phrase(),
9+
},
10+
{
11+
title: 'What is a dimension of a point?',
12+
content: faker.hacker.phrase(),
13+
},
14+
]
15+
16+
const AccordionExample = () => <Accordion panels={panels} />
17+
18+
export default AccordionExample
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react'
2+
import { Accordion } from 'stardust'
3+
4+
const panels = [
5+
{
6+
title: 'One',
7+
content: '2 3 4',
8+
},
9+
{
10+
title: 'Five',
11+
content: '6 7 8 9',
12+
},
13+
{
14+
title: "What's next?",
15+
content: '10',
16+
},
17+
]
18+
19+
const AccordionExclusiveExample = () => <Accordion panels={panels} exclusive />
20+
21+
export default AccordionExclusiveExample
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react'
2+
import ComponentExample from 'docs/src/components/ComponentDoc/ComponentExample'
3+
import ExampleSection from 'docs/src/components/ComponentDoc/ExampleSection'
4+
5+
const Types = () => (
6+
<ExampleSection title="Types">
7+
<ComponentExample
8+
title="Default"
9+
description="A default Accordion."
10+
examplePath="components/Accordion/Types/AccordionExample"
11+
/>
12+
<ComponentExample
13+
title="Exclusive"
14+
description="An exclusive Accordion."
15+
examplePath="components/Accordion/Types/AccordionExclusiveExample"
16+
/>
17+
</ExampleSection>
18+
)
19+
20+
export default Types
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React from 'react'
2+
import { Accordion, List, Image, Button } from 'stardust'
3+
import { render } from 'react-dom'
4+
5+
class AccordionExampleList extends React.Component {
6+
imgStyle = { display: 'block', width: '2rem' }
7+
getAvatar = () => <Image src="//placehold.it/100" style={this.imgStyle} />
8+
9+
firstGroupList = [
10+
{ key: 'g11', media: this.getAvatar(), header: 'cat' },
11+
{ key: 'g12', media: this.getAvatar(), header: 'dog' },
12+
{ key: 'g13', media: this.getAvatar(), header: 'mouse' },
13+
]
14+
15+
render() {
16+
const buttonStyle = { marginLeft: '1.3rem' }
17+
const accContent = [
18+
<List key="firstGroupList" items={this.firstGroupList} />,
19+
<Button key="firstButton" style={buttonStyle}>
20+
Add pet
21+
</Button>,
22+
]
23+
24+
const panels = [{ title: 'Pets', content: accContent }]
25+
26+
return <Accordion panels={panels} />
27+
}
28+
}
29+
30+
export default AccordionExampleList
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react'
2+
import ComponentExample from 'docs/src/components/ComponentDoc/ComponentExample'
3+
import ExampleSection from 'docs/src/components/ComponentDoc/ExampleSection'
4+
5+
const Variations = () => (
6+
<ExampleSection title="Variations">
7+
<ComponentExample
8+
title="Accordion containing lists as children (non exclusive)"
9+
description="An accordion can have different children."
10+
examplePath="components/Accordion/Variations/AccordionExampleList"
11+
/>
12+
</ExampleSection>
13+
)
14+
15+
export default Variations
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from 'react'
2+
import Types from './Types'
3+
import Variations from './Variations'
4+
5+
const AccordionExamples = () => (
6+
<div>
7+
<Types />
8+
<Variations />
9+
</div>
10+
)
11+
12+
export default AccordionExamples
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import _ from 'lodash'
2+
import PropTypes from 'prop-types'
3+
import React from 'react'
4+
import cx from 'classnames'
5+
6+
import {
7+
AutoControlledComponent as Component,
8+
customPropTypes,
9+
createComponent,
10+
getElementType,
11+
getUnhandledProps,
12+
childrenExist,
13+
} from '../../lib'
14+
import accordionRules from './accordionRules'
15+
import AccordionTitle from './AccordionTitle'
16+
import AccordionContent from './AccordionContent'
17+
18+
/**
19+
* A standard Accordion.
20+
*/
21+
class Accordion extends Component {
22+
static propTypes = {
23+
/** An element type to render as (string or function). */
24+
as: customPropTypes.as,
25+
26+
/** Index of the currently active panel. */
27+
activeIndex: customPropTypes.every([
28+
customPropTypes.disallow(['children']),
29+
PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number]),
30+
]),
31+
32+
/** Primary content. */
33+
children: PropTypes.node,
34+
35+
/** Additional classes. */
36+
className: PropTypes.string,
37+
38+
/** Initial activeIndex value. */
39+
defaultActiveIndex: customPropTypes.every([
40+
customPropTypes.disallow(['children']),
41+
PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.number]),
42+
]),
43+
44+
/** Only allow one panel open at a time. */
45+
exclusive: PropTypes.bool,
46+
47+
/**
48+
* Called when a panel title is clicked.
49+
*
50+
* @param {SyntheticEvent} event - React's original SyntheticEvent.
51+
* @param {object} data - All item props.
52+
*/
53+
onTitleClick: customPropTypes.every([customPropTypes.disallow(['children']), PropTypes.func]),
54+
55+
/** A bunch of styles we might not need. */
56+
styles: PropTypes.object,
57+
58+
/** Shorthand array of props for Accordion. */
59+
panels: customPropTypes.every([
60+
customPropTypes.disallow(['children']),
61+
PropTypes.arrayOf(
62+
PropTypes.shape({
63+
content: customPropTypes.itemShorthand,
64+
title: customPropTypes.itemShorthand,
65+
}),
66+
),
67+
]),
68+
}
69+
70+
static handledProps = [
71+
'activeIndex',
72+
'as',
73+
'children',
74+
'className',
75+
'defaultActiveIndex',
76+
'exclusive',
77+
'onTitleClick',
78+
'panels',
79+
'styles',
80+
]
81+
82+
static defaultProps = {
83+
as: 'accordion',
84+
panels: [],
85+
exclusive: false,
86+
}
87+
88+
static autoControlledProps = ['activeIndex']
89+
90+
static Title = AccordionTitle
91+
static Content = AccordionContent
92+
93+
state: any = { activeIndex: [0] }
94+
95+
getInitialAutoControlledState({ exclusive }) {
96+
return { activeIndex: exclusive ? -1 : [-1] }
97+
}
98+
99+
computeNewIndex = index => {
100+
const { activeIndex } = this.state
101+
const { exclusive } = this.props
102+
103+
if (exclusive) return index === activeIndex ? -1 : index
104+
// check to see if index is in array, and remove it, if not then add it
105+
return _.includes(activeIndex, index) ? _.without(activeIndex, index) : [...activeIndex, index]
106+
}
107+
108+
handleTitleOverrides = predefinedProps => ({
109+
onClick: (e, titleProps) => {
110+
const { index } = titleProps
111+
const activeIndex = this.computeNewIndex(index)
112+
113+
this.trySetState({ activeIndex }, index)
114+
115+
_.invoke(predefinedProps, 'onClick', e, titleProps)
116+
_.invoke(this.props, 'onTitleClick', e, titleProps)
117+
},
118+
})
119+
120+
isIndexActive = index => {
121+
const { exclusive } = this.props
122+
const { activeIndex } = this.state
123+
124+
return exclusive ? activeIndex === index : _.includes(activeIndex, index)
125+
}
126+
127+
renderPanels = () => {
128+
const children = []
129+
const { panels } = this.props
130+
131+
_.each(panels, (panel, index) => {
132+
const { content, title } = panel
133+
const active = this.isIndexActive(index)
134+
135+
children.push(
136+
AccordionTitle.create(title, {
137+
autoGenerateKey: true,
138+
defaultProps: { active, index },
139+
overrideProps: this.handleTitleOverrides,
140+
}),
141+
)
142+
children.push(
143+
AccordionContent.create(content, {
144+
autoGenerateKey: true,
145+
defaultProps: { active },
146+
}),
147+
)
148+
})
149+
150+
return children
151+
}
152+
153+
render() {
154+
const { styles, className, children } = this.props
155+
const rest = getUnhandledProps(Accordion, this.props)
156+
const ElementType = getElementType(Accordion, this.props)
157+
158+
return (
159+
<ElementType {...rest} className={cx('ui-accordion', styles.root, className)}>
160+
{childrenExist(children) ? children : this.renderPanels()}
161+
</ElementType>
162+
)
163+
}
164+
}
165+
166+
export default createComponent(Accordion, {
167+
rules: accordionRules,
168+
})
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import _ from 'lodash'
2+
import PropTypes from 'prop-types'
3+
import React from 'react'
4+
import cx from 'classnames'
5+
6+
import {
7+
customPropTypes,
8+
createComponent,
9+
getElementType,
10+
getUnhandledProps,
11+
useKeyOnly,
12+
childrenExist,
13+
} from '../../lib'
14+
import accordionContentRules from './accordionContentRules'
15+
import accordionContentVariables from './accordionContentVariables'
16+
17+
/**
18+
* A standard AccordionContent.
19+
*/
20+
const AccordionContent: any = (props: any) => {
21+
const ElementType = getElementType(AccordionContent, props)
22+
const rest = getUnhandledProps(AccordionContent, props)
23+
const { active, children, className, content, styles } = props
24+
const classes = cx(useKeyOnly(active, 'active'), 'ui-accordionContent', styles.root, className)
25+
26+
return (
27+
<ElementType {...rest} className={classes}>
28+
{childrenExist(children) ? children : content}
29+
</ElementType>
30+
)
31+
}
32+
33+
AccordionContent.propTypes = {
34+
/** An element type to render as (string or function). */
35+
as: customPropTypes.as,
36+
37+
/** Whether or not the content is visible. */
38+
active: PropTypes.bool,
39+
40+
/** Primary content. */
41+
children: PropTypes.node,
42+
43+
/** Additional classes. */
44+
className: PropTypes.string,
45+
46+
/** Shorthand for primary content. */
47+
content: customPropTypes.contentShorthand,
48+
49+
/** A bunch of styles we might not need. */
50+
styles: PropTypes.object,
51+
52+
/**
53+
* Called on click.
54+
*
55+
* @param {SyntheticEvent} event - React's original SyntheticEvent.
56+
* @param {object} data - All props.
57+
*/
58+
onClick: PropTypes.func,
59+
}
60+
61+
AccordionContent.handledProps = ['as', 'className', 'styles', 'onClick']
62+
63+
AccordionContent.defaultProps = {
64+
as: 'accordionContent',
65+
}
66+
67+
export default createComponent(AccordionContent, {
68+
rules: accordionContentRules,
69+
variables: accordionContentVariables,
70+
shorthand: content => ({ content }),
71+
})

0 commit comments

Comments
 (0)