Skip to content
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

ExpandableSection - add minHeight #3040

Merged
merged 6 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file added demo/src/assets/icons/info.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/src/assets/icons/info@1.5x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/src/assets/icons/info@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/src/assets/icons/info@3x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/src/assets/icons/info@4x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
185 changes: 99 additions & 86 deletions demo/src/screens/componentScreens/ExpandableSectionScreen.tsx
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&apos;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 help@help.com, 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>
Copy link
Collaborator

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"

Copy link
Contributor Author

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

Copy link
Collaborator

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"...

<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>
);
}
Expand Down
7 changes: 6 additions & 1 deletion src/components/expandableSection/expandableSection.api.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
"type": "() => void",
"description": "Called when pressing the header of the ExpandableSection"
},
{"name": "testID","type": "string","description": "testing identifier"}
{
"name": "minHeight",
"type": "number",
"description": "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"
},
{"name": "testID", "type": "string", "description": "testing identifier"}
],
"snippet": [
"<ExpandableSection",
Expand Down
59 changes: 42 additions & 17 deletions src/components/expandableSection/index.tsx
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';

Expand All @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added (sectionHeader will not be rendered)

*/
minHeight?: number;
/**
* Testing identifier
*/
Expand All @@ -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]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to break the line here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how prettify is indenting for me

Copy link
Collaborator

Choose a reason for hiding this comment

The 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]);

Expand All @@ -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;
Expand Down