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

[IOAPPFD0-134] Add the new AccordionItem component #43

Merged
merged 12 commits into from
Aug 29, 2023
2 changes: 1 addition & 1 deletion example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ EXTERNAL SOURCES:
:podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
hermes-engine:
:podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
:tag: hermes-2023-03-07-RNv0.71.4-31fdcf738940875c9bacf251e149006cf515d763
:tag: hermes-2023-03-20-RNv0.72.0-49794cfc7c81fb8f69fd60c3bbf85a7480cc5a77
RCT-Folly:
:podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
RCTRequired:
Expand Down
1 change: 1 addition & 0 deletions example/src/navigation/navigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const AppNavigator = () => (
name={APP_ROUTES.COMPONENTS.ACCORDION.route}
component={Accordion}
options={{
presentation: "modal",
headerTitle: APP_ROUTES.COMPONENTS.ACCORDION.title,
headerBackTitleVisible: false
}}
Expand Down
100 changes: 73 additions & 27 deletions example/src/pages/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,90 @@
import {
AccordionItem,
Body,
H3,
H5,
IOAccordion,
IOColors,
IOStyles,
IOVisualCostants,
Icon,
Label,
RawAccordion,
VSpacer
} from "@pagopa/io-app-design-system";
import * as React from "react";
import { View } from "react-native";
import { FlatList, ListRenderItemInfo, View } from "react-native";
import { Screen } from "../components/Screen";

export const Accordion = () => (
<Screen>
<Label>{"<IOAccordion />"}</Label>
<View
style={[
IOStyles.flex,
{ width: "100%", paddingTop: IOVisualCostants.appMarginDefault }
]}
>
<IOAccordion title={"Animated Accordion"}>
<Icon name="productIOApp" size={20} color="grey-650" />
</IOAccordion>
<IOAccordion title={"Accordion without animation"} animated={false}>
<Icon name="productIOApp" size={20} color="grey-650" />
</IOAccordion>
<IOAccordion title={"Accordion with a very very very very long text"}>
<>
<Icon name="productIOApp" size={20} color="grey-650" />
<Icon name="productIOApp" size={20} color="grey-650" />
</>
</IOAccordion>
</View>
const assistanceData: Array<AccordionItem> = [
{
id: 1,
title: "Come posso pagare su IO?",
body: "Puoi pagare con carte di debito, credito e prepagate, con PayPal o BANCOMAT Pay."
},
{
id: 2,
title: "Come posso eliminare un metodo di pagamento?",
body: "I tuoi metodi di pagamento sono visualizzati come card nella parte alta dello schermo del Portafoglio. Seleziona la card del metodo che vuoi eliminare e poi premi 'Elimina questo metodo!"
},
{
id: 3,
title:
"Nel 2021 ho maturato degli importi che non mi sono ancora stati rimborsati. Cosa posso fare?",
body: "Probabilmente non hai indicato l'IBAN del conto su cui desideri ricevere l'accredito, oppure quello che hai indicato non è valido.Per inserire l'IBAN o verificarne la correttezza, apri l'app, vai al Portafoglio e premi sulla card del Cashback. Poi, inseriscilo o modificalo e seleziona 'Continua'. Entro 90 giorni riceverai tramite l'app 10 un messaggio sullo stato del rimborso. Le attività tra PagoPA S.p.A. e Consap S.p.A. per i pagamenti dei rimborsi sono in corso di dismissione, quindi, se non inserisci o non correggi l'IBAN entro il 31/07/2022, potrebbe non essere più possibile effettuare il bonifico per il rimborso."
},
{
id: 4,
title: "Come posso eliminare un metodo di pagamento?",
body: (
<Body>
I tuoi metodi di pagamento sono visualizzati come card nella parte alta
dello schermo del Portafoglio. Seleziona la card del metodo che vuoi
eliminare e poi premi Elimina questo metodo!
</Body>
)
},
{
id: 5,
title:
"Nel 2021 ho maturato degli importi che non mi sono ancora stati rimborsati. Cosa posso fare?",
body: "Probabilmente non hai indicato l'IBAN del conto su cui desideri ricevere l'accredito, oppure quello che hai indicato non è valido.Per inserire l'IBAN o verificarne la correttezza, apri l'app, vai al Portafoglio e premi sulla card del Cashback. Poi, inseriscilo o modificalo e seleziona 'Continua'. Entro 90 giorni riceverai tramite l'app 10 un messaggio sullo stato del rimborso. Le attività tra PagoPA S.p.A. e Consap S.p.A. per i pagamenti dei rimborsi sono in corso di dismissione, quindi, se non inserisci o non correggi l'IBAN entro il 31/07/2022, potrebbe non essere più possibile effettuare il bonifico per il rimborso."
}
];

export const Accordion = () => {
// const renderAccordionHeader = () => (
// <View style={{ marginTop: 16, marginBottom: 24 }}>
// <H1 color={theme["textHeading-default"]}>Accordion</H1>
// </View>
// );

const renderItem = ({ item }: ListRenderItemInfo<AccordionItem>) => (
<AccordionItem id={item.id} title={item.title} body={item.body} />
);

return (
<Screen>
<FlatList
scrollEnabled={false}
data={assistanceData}
contentContainerStyle={{
flexGrow: 1,
paddingTop: IOVisualCostants.appMarginDefault
}}
// ListHeaderComponent={renderAccordionHeader}
ItemSeparatorComponent={() => <VSpacer size={8} />}
keyExtractor={(item, index) => `${item.id}-${index}`}
renderItem={renderItem}
/>

{renderRawAccordion()}
</Screen>
);
};

const renderRawAccordion = () => (
<>
<VSpacer size={40} />
<Label>{"<RawAccordion />"}</Label>
<H3>RawAccordion</H3>
<VSpacer size={16} />
<View style={[IOStyles.flex, { width: "100%" }]}>
<RawAccordion
Expand Down Expand Up @@ -77,5 +123,5 @@ export const Accordion = () => (
</Body>
</RawAccordion>
</View>
</Screen>
</>
);
165 changes: 165 additions & 0 deletions src/components/accordion/AccordionItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import React, { useState } from "react";
import {
Text,
View,
StyleSheet,
TouchableWithoutFeedback,
LayoutChangeEvent
} from "react-native";
import Animated, {
useAnimatedStyle,
withSpring
} from "react-native-reanimated";
import LinearGradient from "react-native-linear-gradient";
import { IOAccordionRadius, type IOSpacingScale } from "../../core";
import { makeFontStyleObject } from "../../utils/fonts";
import { IOColors, hexToRgba } from "../../core/IOColors";
import { H6 } from "../typography";
import { IOSpringValues } from "../../core/IOAnimations";
import { Icon } from "../icons/Icon";

export type AccordionItem = {
id: number;
title: string;
body: string | React.ReactNode;
};

type AccordionBody = {
children: React.ReactNode;
expanded: boolean;
};

const accordionBodySpacing: IOSpacingScale = 16;
const accordionIconMargin: IOSpacingScale = 8;
const accordionBorder: IOColors = "grey-200";
const accordionBackground: IOColors = "white";

/* The code below is a re-adaptation of Dima Portenko's code:
https://github.com/dimaportenko/reanimated-collapsable-card-tutorial
*/
export const AccordionBody = ({ children, expanded }: AccordionBody) => {
const [height, setHeight] = useState(0);

const onLayout = (event: LayoutChangeEvent) => {
const { height: onLayoutHeight } = event.nativeEvent.layout;

if (onLayoutHeight > 0 && height !== onLayoutHeight) {
setHeight(onLayoutHeight);
}
};

const animatedHeightStyle = useAnimatedStyle(
() => ({
height: expanded
? withSpring(height, IOSpringValues.accordion)
: withSpring(0, IOSpringValues.accordion)
}),
[expanded]
);

return (
<Animated.View
style={[animatedHeightStyle, styles.accordionCollapsableContainer]}
>
<View style={styles.accordionBodyContainer} onLayout={onLayout}>
{children}
</View>
</Animated.View>
);
};

export const AccordionItem = ({ title, body }: AccordionItem) => {
const [expanded, setExpanded] = useState(false);

const onItemPress = () => {
setExpanded(!expanded);
};

const animatedChevron = useAnimatedStyle(
() => ({
transform: [
{
rotate: expanded
? withSpring(`180deg`, IOSpringValues.accordion)
: withSpring(`0deg`, IOSpringValues.accordion)
}
]
}),
[expanded]
);

return (
<View style={styles.accordionWrapper}>
<TouchableWithoutFeedback
accessible={true}
accessibilityRole="button"
accessibilityState={{ expanded }}
onPress={onItemPress}
>
<View style={styles.textContainer}>
<View style={{ flexShrink: 1, marginRight: accordionIconMargin }}>
<H6 color="black">{title}</H6>
</View>
<Animated.View style={animatedChevron}>
<Icon name="chevronBottom" color="blueIO-500" />
</Animated.View>
</View>
</TouchableWithoutFeedback>

<AccordionBody expanded={expanded}>
{typeof body === "string" ? (
<Text style={styles.accordionBodyText}>{body}</Text>
) : (
body
)}
</AccordionBody>
{/* This gradient adds a smooth end to the content. If it is missing,
the content will be cut sharply during the height transition. */}
<LinearGradient
style={{
height: accordionBodySpacing,
position: "absolute",
// Place at the bottom
bottom: 0,
// Avoid gradient overlaps with border radius
left: accordionBodySpacing,
right: accordionBodySpacing
}}
colors={[
hexToRgba(IOColors[accordionBackground], 0),
IOColors[accordionBackground]
]}
/>
</View>
);
};

const styles = StyleSheet.create({
accordionWrapper: {
borderColor: IOColors[accordionBorder],
borderWidth: 1,
borderRadius: IOAccordionRadius,
backgroundColor: IOColors[accordionBackground]
},
accordionCollapsableContainer: {
overflow: "hidden"
},
accordionBodyContainer: {
position: "absolute",
padding: accordionBodySpacing,
paddingTop: 0
},
accordionBodyText: {
fontSize: 14,
lineHeight: 21,
color: IOColors["grey-700"],
...makeFontStyleObject("Regular", undefined, "TitilliumWeb")
},
textContainer: {
padding: accordionBodySpacing,
flexGrow: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between"
}
});
35 changes: 0 additions & 35 deletions src/components/accordion/IOAccordion.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion src/components/accordion/index.tsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from "./RawAccordion";
export * from "./IOAccordion";
export * from "./AccordionItem";
5 changes: 5 additions & 0 deletions src/core/IOAnimations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ export const IOSpringValues = {
mass: 0.5,
stiffness: 300
},
accordion: {
damping: 30,
mass: 1,
stiffness: 325
},
/* Used by selection items (checkbox, radio, etc…) */
selection: {
damping: 10,
Expand Down
1 change: 1 addition & 0 deletions src/core/IOShapes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export const IOTagRadius: IORadiusScale = 6;
export const IOBottomSheetHeaderRadius: IORadiusScale = 24;
export const IOListItemIDPRadius: IORadiusScale = IODefaultRadius;
export const IOBadgeRadius: IORadiusScale = 24;
export const IOAccordionRadius: IORadiusScale = IODefaultRadius;