Skip to content

Commit decbedb

Browse files
vieiralucassatya164
authored andcommitted
feat: allow List.Accordion to behave as a controlled component (callstack#638)
fixes callstack#616, callstack#635 closes callstack#618
1 parent 8764f60 commit decbedb

File tree

5 files changed

+336
-27
lines changed

5 files changed

+336
-27
lines changed

example/src/ListAccordionExample.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,28 @@ type Props = {
99
theme: Theme,
1010
};
1111

12-
class ListAccordionExample extends React.Component<Props> {
12+
type State = {
13+
expanded: boolean,
14+
};
15+
16+
class ListAccordionExample extends React.Component<Props, State> {
1317
static title = 'List.Accordion';
1418

19+
state = {
20+
expanded: true,
21+
};
22+
23+
_handlePress = () => {
24+
this.setState({ expanded: !this.state.expanded });
25+
};
26+
1527
render() {
1628
const {
1729
theme: {
1830
colors: { background },
1931
},
2032
} = this.props;
33+
2134
return (
2235
<ScrollView style={[styles.container, { backgroundColor: background }]}>
2336
<List.Section title="Expandable list item">
@@ -28,6 +41,14 @@ class ListAccordionExample extends React.Component<Props> {
2841
<List.Item title="List item 1" />
2942
<List.Item title="List item 2" />
3043
</List.Accordion>
44+
<List.Accordion
45+
left={props => <List.Icon {...props} icon="folder" />}
46+
title="Start expanded"
47+
expanded={this.state.expanded}
48+
onPress={this._handlePress}
49+
>
50+
<List.Item title="List item 1" />
51+
</List.Accordion>
3152
</List.Section>
3253
<Divider />
3354
<List.Section title="Expandable & multiline list item">

src/components/List/ListAccordion.js

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ type Props = {
2222
* Callback which returns a React element to display on the left side.
2323
*/
2424
left?: (props: { color: string }) => React.Node,
25+
/**
26+
* Whether the accordion is expanded
27+
* If this prop is provided, the accordion will behave as a "controlled component".
28+
* You'll need to update this prop when you want to toggle the component or on `onPress`.
29+
*/
30+
expanded?: boolean,
31+
/**
32+
* Function to execute on press.
33+
*/
34+
onPress?: () => mixed,
2535
/**
2636
* Content of the section.
2737
*/
@@ -51,15 +61,40 @@ type State = {
5161
* import * as React from 'react';
5262
* import { List, Checkbox } from 'react-native-paper';
5363
*
54-
* const MyComponent = () => (
55-
* <List.Accordion
56-
* title="Accordion"
57-
* left={props => <List.Icon {...props} icon="folder" />}
58-
* >
59-
* <List.Item title="First item" />
60-
* <List.Item title="Second item" />
61-
* </List.Accordion>
62-
* );
64+
* class MyComponent extends React.Component {
65+
* state = {
66+
* expanded: true
67+
* }
68+
*
69+
* _handlePress = () =>
70+
* this.setState({
71+
* expanded: !this.state.expanded
72+
* });
73+
*
74+
* render() {
75+
* return (
76+
* <List.Section title="Accordions">
77+
* <List.Accordion
78+
* title="Uncontrolled Accordion"
79+
* left={props => <List.Icon {...props} icon="folder" />}
80+
* >
81+
* <List.Item title="First item" />
82+
* <List.Item title="Second item" />
83+
* </List.Accordion>
84+
*
85+
* <List.Accordion
86+
* title="Controlled Accordion"
87+
* left={props => <List.Icon {...props} icon="folder" />}
88+
* expanded={this.state.expanded}
89+
* onPress={this._handlePress}
90+
* >
91+
* <List.Item title="First item" />
92+
* <List.Item title="Second item" />
93+
* </List.Accordion>
94+
* </List.Section>
95+
* );
96+
* }
97+
* }
6398
*
6499
* export default MyComponent;
65100
* ```
@@ -68,13 +103,20 @@ class ListAccordion extends React.Component<Props, State> {
68103
static displayName = 'List.Accordion';
69104

70105
state = {
71-
expanded: false,
106+
expanded: this.props.expanded || false,
72107
};
73108

74-
_handlePress = () =>
75-
this.setState(state => ({
76-
expanded: !state.expanded,
77-
}));
109+
_handlePress = () => {
110+
this.props.onPress && this.props.onPress();
111+
112+
if (this.props.expanded === undefined) {
113+
// Only update state of the `expanded` prop was not passed
114+
// If it was passed, the component will act as a controlled component
115+
this.setState(state => ({
116+
expanded: !state.expanded,
117+
}));
118+
}
119+
};
78120

79121
render() {
80122
const { left, title, description, children, theme, style } = this.props;
@@ -87,6 +129,11 @@ class ListAccordion extends React.Component<Props, State> {
87129
.rgb()
88130
.string();
89131

132+
const expanded =
133+
this.props.expanded !== undefined
134+
? this.props.expanded
135+
: this.state.expanded;
136+
90137
return (
91138
<View>
92139
<TouchableRipple
@@ -99,9 +146,7 @@ class ListAccordion extends React.Component<Props, State> {
99146
<View style={styles.row} pointerEvents="none">
100147
{left
101148
? left({
102-
color: this.state.expanded
103-
? theme.colors.primary
104-
: descriptionColor,
149+
color: expanded ? theme.colors.primary : descriptionColor,
105150
})
106151
: null}
107152
<View style={[styles.item, styles.content]}>
@@ -110,9 +155,7 @@ class ListAccordion extends React.Component<Props, State> {
110155
style={[
111156
styles.title,
112157
{
113-
color: this.state.expanded
114-
? theme.colors.primary
115-
: titleColor,
158+
color: expanded ? theme.colors.primary : titleColor,
116159
},
117160
]}
118161
>
@@ -134,18 +177,14 @@ class ListAccordion extends React.Component<Props, State> {
134177
</View>
135178
<View style={[styles.item, description && styles.multiline]}>
136179
<Icon
137-
source={
138-
this.state.expanded
139-
? 'keyboard-arrow-up'
140-
: 'keyboard-arrow-down'
141-
}
180+
source={expanded ? 'keyboard-arrow-up' : 'keyboard-arrow-down'}
142181
color={titleColor}
143182
size={24}
144183
/>
145184
</View>
146185
</View>
147186
</TouchableRipple>
148-
{this.state.expanded
187+
{expanded
149188
? React.Children.map(children, child => {
150189
if (
151190
left &&

src/components/__tests__/ListAccordion.test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,15 @@ it('renders list accordion with left items', () => {
5353

5454
expect(tree).toMatchSnapshot();
5555
});
56+
57+
it('renders expanded accordion', () => {
58+
const tree = renderer
59+
.create(
60+
<ListAccordion title="Accordion item 1" expanded>
61+
<ListItem title="List item 1" />
62+
</ListAccordion>
63+
)
64+
.toJSON();
65+
66+
expect(tree).toMatchSnapshot();
67+
});

0 commit comments

Comments
 (0)