Commit 10ad7c32 authored by Alexandru Munteanu's avatar Alexandru Munteanu Committed by Yannis Barlas

feat(accordion-component): ui accordion component

parent 2bf8c286
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { th, override } from '@pubsweet/ui-toolkit'
import { withState, withHandlers, compose } from 'recompose'
import { Icon } from '../atoms'
// #region styles
const Root = styled.div`
cursor: pointer;
display: flex;
flex-direction: column;
transition: all ${th('transitionDuration')};
${override('ui.Accordion')};
`
const Header = styled.div.attrs({
'data-test-id': props => props['data-test-id'] || 'accordion-header',
})`
align-items: center;
cursor: pointer;
display: flex;
justify-content: flex-start;
${override('ui.Accordion.Header')};
`
const HeaderLabel = styled.span`
color: ${th('colorPrimary')};
font-family: ${th('fontHeading')};
font-size: ${th('fontSizeBase')};
${override('ui.Accordion.Header.Label')};
`
const HeaderIcon = styled.div`
align-items: center;
display: flex;
justify-content: center;
transform: ${({ expanded }) => `rotateZ(${expanded ? 0 : 180}deg)`};
transition: transform ${th('transitionDuration')};
${override('ui.Accordion.Header.Icon')};
`
// #endregion
const HeaderComponent = ({ icon, expanded, label, toggle, ...props }) => (
<Header expanded={expanded} onClick={toggle} {...props}>
<HeaderIcon expanded={expanded}>
<Icon primary size={3}>
{icon}
</Icon>
</HeaderIcon>
<HeaderLabel>{label}</HeaderLabel>
</Header>
)
const Accordion = ({
toggle,
expanded,
children,
icon = 'chevron_up',
header: Header = HeaderComponent,
...props
}) => (
<Root>
<Header expanded={expanded} icon={icon} toggle={toggle} {...props} />
{expanded && children}
</Root>
)
Accordion.propTypes = {
/** Header icon, from the [Feather](https://feathericons.com/) icon set. */
icon: PropTypes.string,
/** Initial state of the accordion. */
startExpanded: PropTypes.bool,
/** Function called when toggling the accordion. The new state is passed as a paremeter. */
onToggle: PropTypes.func,
}
Accordion.defaultProps = {
onToggle: null,
startExpanded: false,
}
export default compose(
withState('expanded', 'setExpanded', ({ startExpanded }) => startExpanded),
withHandlers({
toggle: ({ expanded, setExpanded, onToggle }) => () => {
setExpanded(!expanded)
if (typeof onToggle === 'function') {
onToggle(!expanded)
}
},
}),
)(Accordion)
A component that can be expanded or collapsed.
```js
<Accordion label="Header" startExpanded>
<hr />
<span>the possibilities</span>
<hr />
<span>are endless</span>
<hr />
<Accordion label="Inception">
<hr />
<span>He's a good kid and a devil behind the wheel.</span>
<hr />
</Accordion>
</Accordion>
```
* Change header icon
```js
<Accordion
data-test-id="test-me"
label="My icon is different"
icon="arrow-down"
>
<hr />
<span>it's ok to be different</span>
<hr />
</Accordion>
```
* Custom header
```js
const MyHeader = ({ toggle }) => {
return <div onClick={toggle} style={{
backgroundColor: 'lavender',
display: 'flex',
justifyContent: 'space-between',
}}>
<span>CUSTOM HEADER SO NICE</span>
<span>Some info here</span>
</div>
}
<Accordion header={MyHeader}>
<hr />
<span>thanos stars</span>
<hr />
<span>in deadpool 2</span>
<hr />
</Accordion>
```
export { default as Accordion } from './Accordion'
export { default as Action } from './Action'
export { default as ActionGroup } from './ActionGroup'
export { default as AlignmentBoxWithLabel } from './AlignmentBoxWithLabel'
......
import React from 'react'
import { shallow, mount } from 'enzyme'
import { Accordion } from '../src/molecules'
describe('<Accordion>', () => {
it('renders without children', () => {
const wrapper = shallow(<Accordion />)
expect(wrapper.children()).toHaveLength(0)
expect(wrapper).toMatchSnapshot()
})
it('toggles children', () => {
const wrapper = mount(
<Accordion>
<span className="child">child 1</span>
<span className="child">child 2</span>
<span className="child">child 3</span>
</Accordion>,
)
wrapper.find({ 'data-test-id': 'accordion-header' }).simulate('click')
expect(wrapper.find('.child')).toHaveLength(3)
wrapper.find({ 'data-test-id': 'accordion-header' }).simulate('click')
expect(wrapper.find('.child')).toHaveLength(0)
})
})
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Accordion> renders without children 1`] = `
ShallowWrapper {
"length": 1,
Symbol(enzyme.__root__): [Circular],
Symbol(enzyme.__unrendered__): <withState(withHandlers(Accordion)) />,
Symbol(enzyme.__renderer__): Object {
"batchedUpdates": [Function],
"getNode": [Function],
"render": [Function],
"simulateEvent": [Function],
"unmount": [Function],
},
Symbol(enzyme.__node__): Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"expanded": undefined,
"setExpanded": [Function],
},
"ref": null,
"rendered": null,
"type": [Function],
},
Symbol(enzyme.__nodes__): Array [
Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"expanded": undefined,
"setExpanded": [Function],
},
"ref": null,
"rendered": null,
"type": [Function],
},
],
Symbol(enzyme.__options__): Object {
"adapter": ReactSixteenAdapter {
"options": Object {
"enableComponentDidUpdateOnSetState": true,
},
},
},
}
`;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment