mirror of
https://github.com/zhigang1992/react-native-paper.git
synced 2026-06-10 15:58:52 +08:00
feat: allow List.Accordion to behave as a controlled component (#638)
fixes #616, #635 closes #618
This commit is contained in:
committed by
Satyajit Sahoo
parent
8764f60370
commit
decbedbd82
@@ -9,15 +9,28 @@ type Props = {
|
||||
theme: Theme,
|
||||
};
|
||||
|
||||
class ListAccordionExample extends React.Component<Props> {
|
||||
type State = {
|
||||
expanded: boolean,
|
||||
};
|
||||
|
||||
class ListAccordionExample extends React.Component<Props, State> {
|
||||
static title = 'List.Accordion';
|
||||
|
||||
state = {
|
||||
expanded: true,
|
||||
};
|
||||
|
||||
_handlePress = () => {
|
||||
this.setState({ expanded: !this.state.expanded });
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
theme: {
|
||||
colors: { background },
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<ScrollView style={[styles.container, { backgroundColor: background }]}>
|
||||
<List.Section title="Expandable list item">
|
||||
@@ -28,6 +41,14 @@ class ListAccordionExample extends React.Component<Props> {
|
||||
<List.Item title="List item 1" />
|
||||
<List.Item title="List item 2" />
|
||||
</List.Accordion>
|
||||
<List.Accordion
|
||||
left={props => <List.Icon {...props} icon="folder" />}
|
||||
title="Start expanded"
|
||||
expanded={this.state.expanded}
|
||||
onPress={this._handlePress}
|
||||
>
|
||||
<List.Item title="List item 1" />
|
||||
</List.Accordion>
|
||||
</List.Section>
|
||||
<Divider />
|
||||
<List.Section title="Expandable & multiline list item">
|
||||
|
||||
@@ -22,6 +22,16 @@ type Props = {
|
||||
* Callback which returns a React element to display on the left side.
|
||||
*/
|
||||
left?: (props: { color: string }) => React.Node,
|
||||
/**
|
||||
* Whether the accordion is expanded
|
||||
* If this prop is provided, the accordion will behave as a "controlled component".
|
||||
* You'll need to update this prop when you want to toggle the component or on `onPress`.
|
||||
*/
|
||||
expanded?: boolean,
|
||||
/**
|
||||
* Function to execute on press.
|
||||
*/
|
||||
onPress?: () => mixed,
|
||||
/**
|
||||
* Content of the section.
|
||||
*/
|
||||
@@ -51,15 +61,40 @@ type State = {
|
||||
* import * as React from 'react';
|
||||
* import { List, Checkbox } from 'react-native-paper';
|
||||
*
|
||||
* const MyComponent = () => (
|
||||
* <List.Accordion
|
||||
* title="Accordion"
|
||||
* left={props => <List.Icon {...props} icon="folder" />}
|
||||
* >
|
||||
* <List.Item title="First item" />
|
||||
* <List.Item title="Second item" />
|
||||
* </List.Accordion>
|
||||
* );
|
||||
* class MyComponent extends React.Component {
|
||||
* state = {
|
||||
* expanded: true
|
||||
* }
|
||||
*
|
||||
* _handlePress = () =>
|
||||
* this.setState({
|
||||
* expanded: !this.state.expanded
|
||||
* });
|
||||
*
|
||||
* render() {
|
||||
* return (
|
||||
* <List.Section title="Accordions">
|
||||
* <List.Accordion
|
||||
* title="Uncontrolled Accordion"
|
||||
* left={props => <List.Icon {...props} icon="folder" />}
|
||||
* >
|
||||
* <List.Item title="First item" />
|
||||
* <List.Item title="Second item" />
|
||||
* </List.Accordion>
|
||||
*
|
||||
* <List.Accordion
|
||||
* title="Controlled Accordion"
|
||||
* left={props => <List.Icon {...props} icon="folder" />}
|
||||
* expanded={this.state.expanded}
|
||||
* onPress={this._handlePress}
|
||||
* >
|
||||
* <List.Item title="First item" />
|
||||
* <List.Item title="Second item" />
|
||||
* </List.Accordion>
|
||||
* </List.Section>
|
||||
* );
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* export default MyComponent;
|
||||
* ```
|
||||
@@ -68,13 +103,20 @@ class ListAccordion extends React.Component<Props, State> {
|
||||
static displayName = 'List.Accordion';
|
||||
|
||||
state = {
|
||||
expanded: false,
|
||||
expanded: this.props.expanded || false,
|
||||
};
|
||||
|
||||
_handlePress = () =>
|
||||
this.setState(state => ({
|
||||
expanded: !state.expanded,
|
||||
}));
|
||||
_handlePress = () => {
|
||||
this.props.onPress && this.props.onPress();
|
||||
|
||||
if (this.props.expanded === undefined) {
|
||||
// Only update state of the `expanded` prop was not passed
|
||||
// If it was passed, the component will act as a controlled component
|
||||
this.setState(state => ({
|
||||
expanded: !state.expanded,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { left, title, description, children, theme, style } = this.props;
|
||||
@@ -87,6 +129,11 @@ class ListAccordion extends React.Component<Props, State> {
|
||||
.rgb()
|
||||
.string();
|
||||
|
||||
const expanded =
|
||||
this.props.expanded !== undefined
|
||||
? this.props.expanded
|
||||
: this.state.expanded;
|
||||
|
||||
return (
|
||||
<View>
|
||||
<TouchableRipple
|
||||
@@ -99,9 +146,7 @@ class ListAccordion extends React.Component<Props, State> {
|
||||
<View style={styles.row} pointerEvents="none">
|
||||
{left
|
||||
? left({
|
||||
color: this.state.expanded
|
||||
? theme.colors.primary
|
||||
: descriptionColor,
|
||||
color: expanded ? theme.colors.primary : descriptionColor,
|
||||
})
|
||||
: null}
|
||||
<View style={[styles.item, styles.content]}>
|
||||
@@ -110,9 +155,7 @@ class ListAccordion extends React.Component<Props, State> {
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
color: this.state.expanded
|
||||
? theme.colors.primary
|
||||
: titleColor,
|
||||
color: expanded ? theme.colors.primary : titleColor,
|
||||
},
|
||||
]}
|
||||
>
|
||||
@@ -134,18 +177,14 @@ class ListAccordion extends React.Component<Props, State> {
|
||||
</View>
|
||||
<View style={[styles.item, description && styles.multiline]}>
|
||||
<Icon
|
||||
source={
|
||||
this.state.expanded
|
||||
? 'keyboard-arrow-up'
|
||||
: 'keyboard-arrow-down'
|
||||
}
|
||||
source={expanded ? 'keyboard-arrow-up' : 'keyboard-arrow-down'}
|
||||
color={titleColor}
|
||||
size={24}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
{this.state.expanded
|
||||
{expanded
|
||||
? React.Children.map(children, child => {
|
||||
if (
|
||||
left &&
|
||||
|
||||
@@ -53,3 +53,15 @@ it('renders list accordion with left items', () => {
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders expanded accordion', () => {
|
||||
const tree = renderer
|
||||
.create(
|
||||
<ListAccordion title="Accordion item 1" expanded>
|
||||
<ListItem title="List item 1" />
|
||||
</ListAccordion>
|
||||
)
|
||||
.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -1,5 +1,240 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders expanded accordion 1`] = `
|
||||
<View>
|
||||
<View
|
||||
accessibilityComponentType="button"
|
||||
accessibilityTraits="button"
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"padding": 8,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
pointerEvents="none"
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"margin": 8,
|
||||
},
|
||||
Object {
|
||||
"flex": 1,
|
||||
"justifyContent": "center",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#000000",
|
||||
"fontFamily": "Helvetica Neue",
|
||||
"textAlign": "left",
|
||||
"writingDirection": "ltr",
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"fontSize": 16,
|
||||
},
|
||||
Object {
|
||||
"color": "#6200ee",
|
||||
},
|
||||
],
|
||||
]
|
||||
}
|
||||
theme={
|
||||
Object {
|
||||
"colors": Object {
|
||||
"accent": "#03dac4",
|
||||
"backdrop": "rgba(0, 0, 0, 0.5)",
|
||||
"background": "#f6f6f6",
|
||||
"disabled": "rgba(0, 0, 0, 0.26)",
|
||||
"error": "#B00020",
|
||||
"placeholder": "rgba(0, 0, 0, 0.54)",
|
||||
"primary": "#6200ee",
|
||||
"surface": "#ffffff",
|
||||
"text": "#000000",
|
||||
},
|
||||
"dark": false,
|
||||
"fonts": Object {
|
||||
"light": "HelveticaNeue-Light",
|
||||
"medium": "HelveticaNeue-Medium",
|
||||
"regular": "Helvetica Neue",
|
||||
"thin": "HelveticaNeue-Thin",
|
||||
},
|
||||
"roundness": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
Accordion item 1
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"margin": 8,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
accessibilityElementsHidden={true}
|
||||
accessible={true}
|
||||
allowFontScaling={false}
|
||||
ellipsizeMode="tail"
|
||||
importantForAccessibility="no-hide-descendants"
|
||||
pointerEvents="none"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "rgba(0, 0, 0, 0.87)",
|
||||
"fontSize": 24,
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"transform": Array [
|
||||
Object {
|
||||
"scaleX": 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "transparent",
|
||||
},
|
||||
],
|
||||
Object {
|
||||
"fontFamily": "Material Icons",
|
||||
"fontStyle": "normal",
|
||||
"fontWeight": "normal",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
accessible={true}
|
||||
isTVSelectable={true}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"padding": 8,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
pointerEvents="none"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"margin": 8,
|
||||
},
|
||||
Object {
|
||||
"flex": 1,
|
||||
"justifyContent": "center",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#000000",
|
||||
"fontFamily": "Helvetica Neue",
|
||||
"textAlign": "left",
|
||||
"writingDirection": "ltr",
|
||||
},
|
||||
Array [
|
||||
Object {
|
||||
"fontSize": 16,
|
||||
},
|
||||
Object {
|
||||
"color": "rgba(0, 0, 0, 0.87)",
|
||||
},
|
||||
],
|
||||
]
|
||||
}
|
||||
theme={
|
||||
Object {
|
||||
"colors": Object {
|
||||
"accent": "#03dac4",
|
||||
"backdrop": "rgba(0, 0, 0, 0.5)",
|
||||
"background": "#f6f6f6",
|
||||
"disabled": "rgba(0, 0, 0, 0.26)",
|
||||
"error": "#B00020",
|
||||
"placeholder": "rgba(0, 0, 0, 0.54)",
|
||||
"primary": "#6200ee",
|
||||
"surface": "#ffffff",
|
||||
"text": "#000000",
|
||||
},
|
||||
"dark": false,
|
||||
"fonts": Object {
|
||||
"light": "HelveticaNeue-Light",
|
||||
"medium": "HelveticaNeue-Medium",
|
||||
"regular": "Helvetica Neue",
|
||||
"thin": "HelveticaNeue-Thin",
|
||||
},
|
||||
"roundness": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
List item 1
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
|
||||
exports[`renders list accordion with children 1`] = `
|
||||
<View>
|
||||
<View
|
||||
|
||||
2
typings/components/List.d.ts
vendored
2
typings/components/List.d.ts
vendored
@@ -4,6 +4,8 @@ import { ThemeShape, IconSource } from '../types';
|
||||
export interface AccordionProps {
|
||||
title: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
expanded?: boolean;
|
||||
onPress?: () => any;
|
||||
left?: (props: { color: string }) => React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
theme?: ThemeShape;
|
||||
|
||||
Reference in New Issue
Block a user