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

[IOPLT-68] Adds accordion components #10

Merged
merged 2 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/components/accordion/IOAccordion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as React from "react";
import { StyleSheet } from "react-native";
import { H3 } from "../typography/H3";
import { IOStyles, IOVisualCostants } from "../../core/IOStyles";
import { RawAccordion } from "./RawAccordion";

type Props = Omit<React.ComponentProps<typeof RawAccordion>, "header"> & {
title: string;
};

const styles = StyleSheet.create({
header: {
marginVertical: IOVisualCostants.appMarginDefault
}
});

/**
* A simplified accordion that accepts a title and one child and uses {@link RawAccordion}
* @param props
* @constructor
*/
export const IOAccordion = (props: Props): React.ReactElement => (
<RawAccordion
animated={props.animated}
headerStyle={styles.header}
accessibilityLabel={props.title}
header={
<H3 numberOfLines={1} style={IOStyles.flex}>
{props.title}
</H3>
}
>
{props.children}
</RawAccordion>
);
116 changes: 116 additions & 0 deletions src/components/accordion/RawAccordion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import * as React from "react";
import { useEffect, useRef, useState } from "react";
import {
View,
AccessibilityProps,
Animated,
Easing,
LayoutAnimation,
StyleSheet,
TouchableWithoutFeedback,
UIManager,
Platform
} from "react-native";
import { IOStyles, IOVisualCostants } from "../../core/IOStyles";
import { Icon } from "../icons/Icon";

// TODO: handle external initial open/closed state
type Props = {
// The header component, an arrow indicating the open/closed state will be added on the right
header: React.ReactElement;
// The accordion component must accept one children
children: React.ReactElement;
// The component should be animated? default: true
animated?: boolean;
headerStyle?: React.ComponentProps<typeof View>["style"];
accessibilityLabel?: AccessibilityProps["accessibilityLabel"];
};

const styles = StyleSheet.create({
headerIcon: {
alignSelf: "center"
},
row: {
...IOStyles.row,
justifyContent: "space-between"
},
internalHeader: {
flex: 1,
paddingRight: IOVisualCostants.appMarginDefault
}
});

/**
* Obtains the degree starting from the open state
* @param isOpen
*/
const getDegree = (isOpen: boolean) => (isOpen ? "-90deg" : "-270deg");

/**
* The base accordion component, implements the opening and closing logic for viewing the children
* @param props
* @constructor
*/
export const RawAccordion: React.FunctionComponent<Props> = props => {
const [isOpen, setOpen] = useState<boolean>(false);
const animatedController = useRef(new Animated.Value(1)).current;
const shouldAnimate = props.animated ?? true;
const headerStyle = props.headerStyle ?? {};
const accessibilityLabel = props.accessibilityLabel
? `${props.accessibilityLabel}, `
: "";

const arrowAngle = shouldAnimate
? animatedController.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "-180deg"]
})
: getDegree(isOpen);

useEffect(() => {
if (Platform.OS === "android") {
UIManager.setLayoutAnimationEnabledExperimental(shouldAnimate);
}
}, [shouldAnimate]);

const onPress = () => {
if (shouldAnimate) {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
Animated.timing(animatedController, {
duration: 300,
toValue: isOpen ? 1 : 0,
useNativeDriver: true,
easing: Easing.linear
}).start();
}
setOpen(!isOpen);
};

return (
<View style={IOStyles.flex}>
<TouchableWithoutFeedback
onPress={onPress}
accessible={true}
accessibilityRole={"button"}
accessibilityLabel={
accessibilityLabel +
(isOpen ? "Paragrafo espanso" : "Paragrafo chiuso")
}
>
<View style={[styles.row, headerStyle]}>
<View style={styles.internalHeader}>{props.header}</View>
<Animated.View
testID={"ArrowAccordion"}
style={{
...styles.headerIcon,
transform: [{ rotateZ: arrowAngle }]
}}
>
<Icon name="chevronTop" color="blue" size={24} />
</Animated.View>
</View>
</TouchableWithoutFeedback>
{isOpen && props.children}
</View>
);
};
2 changes: 2 additions & 0 deletions src/components/accordion/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./RawAccordion";
export * from "./IOAccordion";
1 change: 1 addition & 0 deletions src/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from "./switch";
export * from "./spacer/Spacer";
export * from "./divider/Divider";
export * from "./contentWrapper/ContentWrapper";
export * from "./accordion";