Skip to content

Commit

Permalink
Merge branch 'master' into IOAPPFD0-174-loading-indicator
Browse files Browse the repository at this point in the history
  • Loading branch information
CrisTofani authored Oct 13, 2023
2 parents 5516bb8 + 3606446 commit 708f7d2
Show file tree
Hide file tree
Showing 2 changed files with 310 additions and 0 deletions.
248 changes: 248 additions & 0 deletions ts/components/ModuleAttachment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import React, { useCallback } from "react";
import {
ActivityIndicator,
GestureResponderEvent,
Pressable,
PressableProps,
StyleSheet,
View
} from "react-native";
import {
IOColors,
IOIconSizeScale,
IOIcons,
IOListItemVisualParams,
IOScaleValues,
IOSpacingScale,
IOSpringValues,
IOStyles,
Icon,
LabelSmall,
VSpacer,
WithTestID
} from "@pagopa/io-app-design-system";
import Animated, {
Extrapolate,
interpolate,
useAnimatedStyle,
useDerivedValue,
useSharedValue,
withSpring
} from "react-native-reanimated";
import Placeholder from "rn-placeholder";
import I18n from "../i18n";

type PartialProps = WithTestID<{
title: string;
format: "doc" | "pdf";
subtitle?: string;
isLoading?: boolean;
isFetching?: boolean;
onPress: (event: GestureResponderEvent) => void;
}>;

export type ModuleAttachmentProps = PartialProps &
Pick<PressableProps, "onPress" | "accessibilityLabel" | "disabled">;

const formatMap: Record<
NonNullable<ModuleAttachmentProps["format"]>,
IOIcons
> = {
doc: "docAttach",
pdf: "docAttachPDF"
};

const styles = StyleSheet.create({
button: {
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: 16,
paddingVertical: 16,
borderRadius: 8,
borderColor: IOColors.bluegreyLight,
backgroundColor: IOColors.white,
borderStyle: "solid",
borderWidth: 1
},
rightSection: {
marginLeft: IOListItemVisualParams.iconMargin,
flexDirection: "row",
alignItems: "center",
justifyContent: "flex-end"
}
});

const DISABLED_OPACITY = 0.5;
const ICON_SIZE: IOIconSizeScale = 32;
const MARGIN_SIZE: IOSpacingScale = 16;

const ModuleAttachmentContent = ({
isFetching,
format,
title,
subtitle
}: Pick<
ModuleAttachmentProps,
"isFetching" | "format" | "title" | "subtitle"
>) => {
const IconOrActivityIndicatorComponent = () => {
if (isFetching) {
return (
<ActivityIndicator
color={IOColors.blue}
accessible={true}
accessibilityHint={I18n.t(
"global.accessibility.activityIndicator.hint"
)}
accessibilityLabel={I18n.t(
"global.accessibility.activityIndicator.label"
)}
importantForAccessibility={"no-hide-descendants"}
testID={"activityIndicator"}
/>
);
}

return (
<Icon
name="chevronRightListItem"
color="blue"
size={IOListItemVisualParams.chevronSize}
/>
);
};

return (
<>
<View style={{ marginRight: MARGIN_SIZE }}>
<Icon name={formatMap[format]} size={ICON_SIZE} color="blue" />
</View>
<View style={IOStyles.flex}>
<LabelSmall numberOfLines={1} weight="SemiBold" color="bluegrey">
{title}
</LabelSmall>
{subtitle && (
<LabelSmall weight="Regular" color="bluegrey">
{subtitle}
</LabelSmall>
)}
</View>
<View style={styles.rightSection}>
<IconOrActivityIndicatorComponent />
</View>
</>
);
};

/**
* The `ModuleAttachment` component is a custom button component with an extended outline style.
* It provides an animated scaling effect when pressed.
*
* @param {boolean} isLoading - If true, displays a skeleton loading component.
* @param {boolean} isFetching - If true, displays an activity indicator.
* @param {boolean} disabled - If true, the button is disabled.
* @param {string} testID - The test ID for testing purposes.
* @param {string} title - The title text to display.
* @param {string} subtitle - The subtitle text to display.
* @param {string} iconName - The icon name to display.
* @param {function} onPress - The function to be executed when the item is pressed.
*/
export const ModuleAttachment = ({
isLoading = false,
isFetching = false,
disabled = false,
testID,
accessibilityLabel,
onPress,
...rest
}: ModuleAttachmentProps) => {
const isPressed: Animated.SharedValue<number> = useSharedValue(0);

// Scaling transformation applied when the button is pressed
const animationScaleValue = IOScaleValues?.magnifiedButton?.pressedState;

const scaleTraversed = useDerivedValue(() =>
withSpring(isPressed.value, IOSpringValues.button)
);

// Interpolate animation values from `isPressed` values
const animatedStyle = useAnimatedStyle(() => {
const scale = interpolate(
scaleTraversed.value,
[0, 1],
[1, animationScaleValue],
Extrapolate.CLAMP
);

return {
transform: [{ scale }]
};
});

const onPressIn = useCallback(() => {
// eslint-disable-next-line functional/immutable-data
isPressed.value = 1;
}, [isPressed]);

const onPressOut = useCallback(() => {
// eslint-disable-next-line functional/immutable-data
isPressed.value = 0;
}, [isPressed]);

const handleOnPress = useCallback(
(event: GestureResponderEvent) => {
if (isFetching) {
return;
}
onPress(event);
},
[isFetching, onPress]
);

if (isLoading) {
return <SkeletonComponent />;
}

return (
<Pressable
testID={testID}
onPress={handleOnPress}
onPressIn={onPressIn}
onPressOut={onPressOut}
accessible={true}
accessibilityRole={"button"}
accessibilityLabel={accessibilityLabel}
disabled={disabled || isFetching}
>
<Animated.View
style={[
styles.button,
animatedStyle,
{ opacity: disabled ? DISABLED_OPACITY : 1 }
]}
>
<ModuleAttachmentContent isFetching={isFetching} {...rest} />
</Animated.View>
</Pressable>
);
};

const SkeletonComponent = () => (
<View style={styles.button} accessible={false}>
<View style={{ marginRight: MARGIN_SIZE }}>
<Placeholder.Box
animate="fade"
height={ICON_SIZE}
width={ICON_SIZE}
radius={100}
/>
</View>
<View style={IOStyles.flex}>
<Placeholder.Box animate="fade" radius={8} width={107} height={16} />
<VSpacer size={4} />
<Placeholder.Box animate="fade" radius={8} width={62} height={16} />
</View>
</View>
);
62 changes: 62 additions & 0 deletions ts/features/design-system/core/DSModules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
PaymentNoticeStatus,
VSpacer
} from "@pagopa/io-app-design-system";
import { ModuleAttachment } from "../../../components/ModuleAttachment";
import { getBadgeTextByPaymentNoticeStatus } from "../../messages/utils/strings";
import { H2 } from "../../../components/core/typography/H2";
import { DSComponentViewerBox } from "../components/DSComponentViewerBox";
Expand All @@ -34,6 +35,17 @@ export const DSModules = () => (
<IOThemeContext.Consumer>
{theme => (
<DesignSystemScreen title="Modules">
<H2
color={theme["textHeading-default"]}
weight={"SemiBold"}
style={{ marginBottom: 16 }}
>
ModuleAttachment
</H2>
{renderModuleAttachment()}

<VSpacer size={40} />

<H2
color={theme["textHeading-default"]}
weight={"SemiBold"}
Expand Down Expand Up @@ -69,6 +81,56 @@ export const DSModules = () => (
</IOThemeContext.Consumer>
);

const renderModuleAttachment = () => (
<DSComponentViewerBox name="ModuleAttachment">
<View>
<ModuleAttachment
title="Nome del documento.pdf"
subtitle="123 Kb"
format="pdf"
isLoading={true}
onPress={onButtonPress}
/>
<VSpacer size={16} />
<ModuleAttachment
title="Nome del documento.pdf"
subtitle="123 Kb"
format="pdf"
onPress={onButtonPress}
/>
<VSpacer size={16} />
<ModuleAttachment
title="Nome del documento.pdf"
format="pdf"
onPress={onButtonPress}
/>
<VSpacer size={16} />
<ModuleAttachment
title={"This is a very loooooooooooooooooooooong title"}
subtitle={"This is a very loooooooooooong subtitle"}
format="pdf"
onPress={onButtonPress}
/>
<VSpacer size={16} />
<ModuleAttachment
title="Nome del documento.pdf"
subtitle="123 Kb"
format="pdf"
isFetching={true}
onPress={onButtonPress}
/>
<VSpacer size={16} />
<ModuleAttachment
title="Nome del documento.pdf"
subtitle="123 Kb"
format="pdf"
disabled={true}
onPress={onButtonPress}
/>
</View>
</DSComponentViewerBox>
);

const renderModulePaymentNotice = () => (
<DSComponentViewerBox name="ModulePaymentNotice">
<View>
Expand Down

0 comments on commit 708f7d2

Please sign in to comment.