-
Notifications
You must be signed in to change notification settings - Fork 734
ExpandableSection - add minHeight #3040
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
f79f8f1
3876d3c
f3acf7e
a5d60c7
0a83d14
086bded
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,119 +1,132 @@ | ||
import _ from 'lodash'; | ||
import React, {PureComponent} from 'react'; | ||
import {ScrollView, StyleSheet} from 'react-native'; | ||
import {Card, Text, Image, ListItem, Carousel, View, ExpandableSection, Switch} from 'react-native-ui-lib'; | ||
import {Text, View, ExpandableSection, SegmentedControl, Colors, Icon} from 'react-native-ui-lib'; | ||
|
||
const cardImage2 = require('../../assets/images/empty-state.jpg'); | ||
const cardImage = require('../../assets/images/card-example.jpg'); | ||
const chevronDown = require('../../assets/icons/chevronDown.png'); | ||
const chevronUp = require('../../assets/icons/chevronUp.png'); | ||
const infoIcon = require('../../assets/icons/info.png'); | ||
const DEFAULT = undefined; | ||
const PARTIALLY_EXPANDED_HEIGHT = 100; | ||
const FULLY_EXPANDED_HEIGHT = 300; | ||
|
||
class ExpandableSectionScreen extends PureComponent { | ||
state = { | ||
expanded: false, | ||
top: false | ||
minHeight: DEFAULT | ||
}; | ||
|
||
elements = [ | ||
<Card key={0} style={{marginBottom: 10}} onPress={() => this.onExpand()}> | ||
<Card.Section | ||
content={[ | ||
{text: 'Card #1', text70: true, grey10: true}, | ||
{text: 'card description', text90: true, grey50: true} | ||
]} | ||
style={{padding: 20}} | ||
/> | ||
<Card.Section imageSource={cardImage2} imageStyle={{height: 120}}/> | ||
</Card>, | ||
<Card key={1} style={{marginBottom: 10}} onPress={() => this.onExpand()}> | ||
<Card.Section | ||
content={[ | ||
{text: 'Card #2', text70: true, grey10: true}, | ||
{text: 'card description', text90: true, grey50: true} | ||
]} | ||
style={{padding: 20}} | ||
/> | ||
<Card.Section imageSource={cardImage} imageStyle={{height: 120}}/> | ||
</Card>, | ||
<Card key={2} style={{marginBottom: 10}} onPress={() => this.onExpand()}> | ||
<Card.Section | ||
content={[ | ||
{text: 'Card #3', text70: true, grey10: true}, | ||
{text: 'card description', text90: true, grey50: true} | ||
]} | ||
style={{padding: 20}} | ||
/> | ||
<Card.Section imageSource={cardImage2} imageStyle={{height: 120}}/> | ||
</Card> | ||
]; | ||
|
||
onExpand() { | ||
onExpand = () => { | ||
this.setState({ | ||
expanded: !this.state.expanded | ||
}); | ||
} | ||
}; | ||
|
||
getChevron() { | ||
if (this.state.expanded) { | ||
return this.state.top ? chevronDown : chevronUp; | ||
} | ||
return this.state.top ? chevronUp : chevronDown; | ||
getChevron(expanded: boolean) { | ||
return expanded ? chevronUp : chevronDown; | ||
} | ||
|
||
getHeaderElement() { | ||
renderReadMoreHeader = () => { | ||
const {expanded} = this.state; | ||
return ( | ||
<View marginH-page marginT-10 row> | ||
<Text text80 marginL-40 marginR-5 $textPrimary> | ||
Read More | ||
</Text> | ||
<Icon style={styles.icon} source={this.getChevron(expanded)} tintColor={Colors.$iconPrimary}/> | ||
</View> | ||
); | ||
}; | ||
|
||
renderHeader = (text: string, | ||
expanded: boolean, | ||
{disabled, showInfo}: {disabled?: boolean; showInfo?: boolean} = {}) => { | ||
return ( | ||
<View marginH-page marginV-20 spread row> | ||
<View row> | ||
{showInfo ? <Icon source={infoIcon} marginR-10 tintColor={disabled ? Colors.grey40 : undefined}/> : null} | ||
<Text text60 marginL-4 grey40={disabled}> | ||
{text} | ||
</Text> | ||
</View> | ||
<Icon style={styles.icon} source={this.getChevron(expanded)} tintColor={disabled ? Colors.grey40 : undefined}/> | ||
</View> | ||
); | ||
}; | ||
|
||
renderContent() { | ||
return ( | ||
<View margin-10 spread row> | ||
<Text grey10 text60> | ||
ExpandableSection sectionHeader | ||
<View marginH-60> | ||
<Text text80> | ||
If you have any questions, comments, or concerns, please don't hesitate to get in touch with us. You can | ||
easily reach out to us through our contact form on our website. | ||
</Text> | ||
<Text text80> | ||
Alternatively, you can reach us via email at [email protected], where our team is ready to assist you promptly. If | ||
you prefer speaking with someone directly, feel free to give us a call at 1-833-350-1066. | ||
</Text> | ||
<Image style={styles.icon} source={this.getChevron()}/> | ||
</View> | ||
); | ||
} | ||
|
||
getBodyElement() { | ||
renderOptions = () => { | ||
return ( | ||
<Carousel> | ||
{_.map(this.elements, (element, key) => { | ||
return ( | ||
<View key={key} margin-12> | ||
{element} | ||
</View> | ||
); | ||
})} | ||
</Carousel> | ||
<View margin-page> | ||
<Text text70BO marginB-8> | ||
Minimum Height | ||
</Text> | ||
<Text text80 marginB-16> | ||
The expandable section can be either fully collapsed, partially expanded to reveal some of the items, or fully | ||
expanded by default. | ||
</Text> | ||
<SegmentedControl | ||
activeColor={Colors.$textDefaultLight} | ||
activeBackgroundColor={Colors.$backgroundInverted} | ||
segments={[{label: 'Default'}, {label: 'Partially'}, {label: 'Fully Expanded'}]} | ||
onChangeIndex={index => { | ||
switch (index) { | ||
case 0: | ||
return this.setState({minHeight: DEFAULT}); | ||
case 1: | ||
return this.setState({minHeight: PARTIALLY_EXPANDED_HEIGHT}); | ||
case 2: | ||
return this.setState({minHeight: FULLY_EXPANDED_HEIGHT}); | ||
} | ||
}} | ||
/> | ||
</View> | ||
); | ||
}; | ||
|
||
renderExpandableSection = () => { | ||
const {expanded, minHeight} = this.state; | ||
return ( | ||
<ExpandableSection | ||
top={minHeight === PARTIALLY_EXPANDED_HEIGHT} | ||
expanded={expanded} | ||
sectionHeader={ | ||
minHeight === PARTIALLY_EXPANDED_HEIGHT | ||
? this.renderReadMoreHeader() | ||
: this.renderHeader('How can I contact you?', expanded, {showInfo: true}) | ||
} | ||
onPress={this.onExpand} | ||
minHeight={minHeight} | ||
> | ||
{this.renderContent()} | ||
</ExpandableSection> | ||
); | ||
}; | ||
|
||
renderNextItem() { | ||
return this.renderHeader('Where are you located?', false, {disabled: true, showInfo: true}); | ||
} | ||
|
||
render() { | ||
const {expanded, top} = this.state; | ||
|
||
const {minHeight} = this.state; | ||
return ( | ||
<ScrollView> | ||
<View row center margin-20> | ||
<Text grey10 text70 marginR-10> | ||
Open section on top | ||
</Text> | ||
<Switch | ||
value={this.state.top} | ||
onValueChange={() => { | ||
this.setState({top: !this.state.top}); | ||
}} | ||
/> | ||
</View> | ||
<ExpandableSection | ||
top={top} | ||
expanded={expanded} | ||
sectionHeader={this.getHeaderElement()} | ||
onPress={() => this.onExpand()} | ||
> | ||
{this.getBodyElement()} | ||
</ExpandableSection> | ||
<ListItem> | ||
<Text grey10 text60 marginL-10> | ||
{'The next item'} | ||
</Text> | ||
</ListItem> | ||
{this.renderOptions()} | ||
{this.renderExpandableSection()} | ||
{minHeight !== PARTIALLY_EXPANDED_HEIGHT ? this.renderNextItem() : null} | ||
</ScrollView> | ||
); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import React, {useMemo, useState} from 'react'; | ||
import React, {useCallback, useMemo, useState} from 'react'; | ||
import {LayoutChangeEvent, StyleSheet} from 'react-native'; | ||
import {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; | ||
import {useDidUpdate} from '../../hooks'; | ||
import View from '../view'; | ||
import TouchableOpacity from '../touchableOpacity'; | ||
|
||
|
@@ -25,6 +26,12 @@ export type ExpandableSectionProps = { | |
* action for when pressing the header of the expandableSection | ||
*/ | ||
onPress?: () => void; | ||
/** | ||
* Set a minimum height for the expandableSection | ||
* If the children height is less than the minHeight, the expandableSection will collapse to that height | ||
* If the children height is greater than the minHeight, the expandableSection will result with only the children rendered | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not clear "will result with only the children rendered". Do you mean "no height limit will apply"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added |
||
*/ | ||
minHeight?: number; | ||
/** | ||
* Testing identifier | ||
*/ | ||
|
@@ -38,25 +45,39 @@ export type ExpandableSectionProps = { | |
*/ | ||
|
||
function ExpandableSection(props: ExpandableSectionProps) { | ||
const {expanded, sectionHeader, onPress, children, top, testID} = props; | ||
const {minHeight, expanded, sectionHeader, onPress, children, top, testID} = props; | ||
const [height, setHeight] = useState(0); | ||
const animatedHeight = useSharedValue(0); | ||
const shouldShowSectionHeader = !minHeight || height > minHeight; | ||
|
||
const onLayout = (event: LayoutChangeEvent) => { | ||
const onLayoutHeight = event.nativeEvent.layout.height; | ||
const layoutHeight = event.nativeEvent.layout.height; | ||
|
||
if (onLayoutHeight > 0 && height !== onLayoutHeight) { | ||
setHeight(onLayoutHeight); | ||
if (layoutHeight > 0 && height !== layoutHeight) { | ||
setHeight(layoutHeight); | ||
} | ||
}; | ||
|
||
const expandableStyle = useAnimatedStyle(() => { | ||
animatedHeight.value = expanded ? withTiming(height) : withTiming(0); | ||
const animateHeight = useCallback((shouldAnimate = true) => { | ||
const collapsedHeight = Math.min(minHeight ?? 0, height); | ||
const toValue = expanded ? height : collapsedHeight; | ||
animatedHeight.value = shouldAnimate ? withTiming(toValue) : toValue; | ||
}, | ||
[animatedHeight, expanded, height, minHeight]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no need to break the line here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is how prettify is indenting for me There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Weired |
||
|
||
useDidUpdate(() => { | ||
animateHeight(false); | ||
}, [height, minHeight]); | ||
|
||
useDidUpdate(() => { | ||
animateHeight(); | ||
}, [expanded]); | ||
|
||
const expandableStyle = useAnimatedStyle(() => { | ||
return { | ||
height: animatedHeight.value | ||
}; | ||
}, [expanded, height]); | ||
}, []); | ||
|
||
const style = useMemo(() => [styles.hidden, expandableStyle], [expandableStyle]); | ||
|
||
|
@@ -74,15 +95,19 @@ function ExpandableSection(props: ExpandableSectionProps) { | |
); | ||
}; | ||
|
||
return ( | ||
<View style={styles.hidden}> | ||
{top && renderChildren()} | ||
<TouchableOpacity onPress={onPress} testID={testID} accessibilityState={accessibilityState}> | ||
{sectionHeader} | ||
</TouchableOpacity> | ||
{!top && renderChildren()} | ||
</View> | ||
); | ||
if (shouldShowSectionHeader) { | ||
return ( | ||
<View style={styles.hidden}> | ||
{top && renderChildren()} | ||
<TouchableOpacity onPress={onPress} testID={testID} accessibilityState={accessibilityState}> | ||
{sectionHeader} | ||
</TouchableOpacity> | ||
{!top && renderChildren()} | ||
</View> | ||
); | ||
} else { | ||
return renderChildren(); | ||
} | ||
} | ||
|
||
export default ExpandableSection; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing our usual title "ExpandableSection"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some screens do not have that and we have it in the RNN title
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know, but it's strange the the title is "Minimum Height"...