diff --git a/example/src/pages/ListItem.tsx b/example/src/pages/ListItem.tsx
index eb72df76..aec3d446 100644
--- a/example/src/pages/ListItem.tsx
+++ b/example/src/pages/ListItem.tsx
@@ -2,6 +2,7 @@ import {
ButtonLink,
H2,
IOThemeContext,
+ Icon,
IconButton,
ListItemAction,
ListItemIDP,
@@ -380,84 +381,94 @@ const renderListItemIDP = () => (
>
);
-const renderListItemTransaction = () => (
-
-
-
-
-
-
-
-
-
-
- }
- transactionAmount=""
- onPress={onButtonPress}
- />
- }
- transactionAmount="€ 100"
- onPress={onButtonPress}
- />
-
-
-);
+const renderListItemTransaction = () => {
+ const cdnPath = "https://assets.cdn.io.italia.it/logos/organizations/";
+ const organizationLogoURI = {
+ imageSource: `${cdnPath}82003830161.png`,
+ name: "Comune di Milano"
+ };
+ return (
+
+
+
+
+
+
+
+
+
+
+ }
+ transactionAmount=""
+ onPress={onButtonPress}
+ />
+ }
+ transactionAmount="€ 100"
+ onPress={onButtonPress}
+ />
+
+
+ );
+};
diff --git a/src/components/common/LogoPaymentWithFallback.tsx b/src/components/common/LogoPaymentWithFallback.tsx
new file mode 100644
index 00000000..2dbfb6fd
--- /dev/null
+++ b/src/components/common/LogoPaymentWithFallback.tsx
@@ -0,0 +1,60 @@
+import * as O from "fp-ts/lib/Option";
+import { pipe } from "fp-ts/lib/function";
+import * as React from "react";
+import { findFirstCaseInsensitive } from "../../utils/object";
+import { IOColors } from "../../core";
+import { IOIconSizeScale, Icon } from "../icons";
+import {
+ IOLogoPaymentExtType,
+ IOLogoPaymentType,
+ IOPaymentExtLogos,
+ IOPaymentLogos,
+ LogoPayment,
+ LogoPaymentExt
+} from "../logos";
+
+export type LogoPaymentWithFallback = {
+ brand?: string;
+ fallbackIconColor?: IOColors;
+ size?: IOIconSizeScale;
+ isExtended?: boolean;
+};
+export type LogoPaymentExtOrDefaultIconProps = {
+ cardIcon?: IOLogoPaymentExtType;
+ fallbackIconColor?: IOColors;
+ size?: IOIconSizeScale;
+};
+/**
+ * This component renders either
+ * - a LogoPayment/LogoPaymentExt component
+ * - a default credit card icon
+ * @param cardIcon: IOLogoPaymentType icon
+ * @param size: the size of the icon (standard is 24/48)
+ * @param fallbackIconColor: default icon color (standard is grey-700)
+ * @param isExtended: if true, renders a LogoPaymentExt component
+ * @returns a LogoPayment/LogopaymentExt component if the cardIcon is supported, a default credit card icon otherwise
+ */
+export const LogoPaymentWithFallback = ({
+ brand,
+ fallbackIconColor = "grey-700",
+ isExtended = false,
+ size = isExtended ? 48 : 24
+}: LogoPaymentWithFallback) => {
+ const logos = isExtended ? IOPaymentExtLogos : IOPaymentLogos;
+
+ return pipe(
+ brand,
+ O.fromNullable,
+ O.chain(findFirstCaseInsensitive(logos)),
+ O.map(([brand]) => brand),
+ O.fold(
+ () => ,
+ brand =>
+ isExtended ? (
+
+ ) : (
+
+ )
+ )
+ );
+};
diff --git a/src/components/listitems/ListItemTransaction.tsx b/src/components/listitems/ListItemTransaction.tsx
index 2346c89e..244b8216 100644
--- a/src/components/listitems/ListItemTransaction.tsx
+++ b/src/components/listitems/ListItemTransaction.tsx
@@ -3,19 +3,25 @@ import { pipe } from "fp-ts/lib/function";
import React from "react";
import { ImageURISource, StyleSheet, View } from "react-native";
import Placeholder from "rn-placeholder";
+
import {
IOColors,
+ IOListItemLogoMargin,
IOListItemStyles,
IOListItemVisualParams,
IOStyles,
IOVisualCostants,
useIOTheme
} from "../../core";
+
+import { LogoPaymentWithFallback } from "../common/LogoPaymentWithFallback";
+import { isImageUri } from "../../utils/url";
import { WithTestID } from "../../utils/types";
+import { getAccessibleAmountText } from "../../utils/accessibility";
import { Avatar } from "../avatar/Avatar";
import { Badge } from "../badge/Badge";
-import { Icon } from "../icons";
-import { IOLogoPaymentType, LogoPayment } from "../logos";
+import { IOIconSizeScale, Icon } from "../icons";
+import { IOLogoPaymentType } from "../logos";
import { VSpacer } from "../spacer";
import { H6, LabelSmall } from "../typography";
import {
@@ -23,95 +29,135 @@ import {
PressableListItemBase
} from "./PressableListItemsBase";
-type LogoNameOrUri = IOLogoPaymentType | ImageURISource;
+export type ListItemTransactionStatus =
+ | "success"
+ | "failure"
+ | "pending"
+ | "cancelled"
+ | "refunded"
+ | "reversal";
+
+type PaymentLogoIcon = IOLogoPaymentType | ImageURISource | React.ReactNode;
+
export type ListItemTransaction = WithTestID<
PressableBaseProps & {
hasChevronRight?: boolean;
isLoading?: boolean;
- paymentLogoOrUrl?: LogoNameOrUri;
+ /**
+ * A logo that will be displayed on the left of the list item.
+ *
+ * Must be a {@link IOLogoPaymentType} or an {@link ImageURISource} or an {@link Icon}.
+ */
+ paymentLogoIcon?: PaymentLogoIcon;
subtitle: string;
title: string;
} & (
| {
- transactionStatus: "success";
+ transactionStatus: "success" | "refunded";
+ badgeText?: string;
transactionAmount: string;
}
| {
- transactionStatus: "failure" | "pending";
+ transactionStatus: "failure" | "pending" | "cancelled" | "reversal";
+ badgeText: string;
transactionAmount?: string;
}
)
>;
type LeftComponentProps = {
- logoNameOrUrl: LogoNameOrUri;
+ logoIcon: PaymentLogoIcon;
};
-const isImageUrI = (
- value: IOLogoPaymentType | ImageURISource
-): value is ImageURISource =>
- typeof value === "object" && value.uri !== undefined;
+const CARD_LOGO_SIZE: IOIconSizeScale = 24;
+const MUNICIPALITY_LOGO_SIZE = 44;
+// this is the 's "small" size,
+// since it is bigger than the card logos, we use
+// it as a base size for homogeneous sizing via container size.
-const LeftComponent = ({ logoNameOrUrl }: LeftComponentProps) => {
- if (isImageUrI(logoNameOrUrl)) {
- return ;
- } else {
- return (
-
-
-
- );
+const LeftComponent = ({ logoIcon }: LeftComponentProps) => {
+ if (isImageUri(logoIcon)) {
+ return ;
+ }
+ if (React.isValidElement(logoIcon)) {
+ return <>{logoIcon}>;
}
+ return (
+
+ );
};
export const ListItemTransaction = ({
accessibilityLabel,
hasChevronRight = false,
isLoading = false,
- paymentLogoOrUrl,
+ paymentLogoIcon,
onPress,
subtitle,
testID,
title,
transactionAmount,
+ badgeText,
transactionStatus = "success"
}: ListItemTransaction) => {
const theme = useIOTheme();
+ const maybeBadgeText = pipe(
+ badgeText,
+ O.fromNullable,
+ O.getOrElse(() => "-")
+ );
+
if (isLoading) {
return ;
}
- const designSystemBlue: IOColors = "blue";
+ const designSystemBlue: IOColors = "blueIO-500";
const ListItemTransactionContent = () => {
const TransactionAmountOrBadgeComponent = () => {
switch (transactionStatus) {
case "success":
return (
-
- {transactionAmount || "-"}
+
+ {transactionAmount || ""}
+
+ );
+ case "refunded":
+ return (
+
+ {transactionAmount || ""}
);
-
case "failure":
- return ;
+ case "cancelled":
+ return ;
+ case "reversal":
+ return ;
case "pending":
- return ;
+ return ;
}
};
return (
<>
- {paymentLogoOrUrl && (
-
-
+ {paymentLogoIcon && (
+
+
)}
@@ -121,7 +167,6 @@ export const ListItemTransaction = ({
{subtitle}
-
diff --git a/src/utils/accessibility.ts b/src/utils/accessibility.ts
new file mode 100644
index 00000000..31da6295
--- /dev/null
+++ b/src/utils/accessibility.ts
@@ -0,0 +1,17 @@
+import { pipe } from "fp-ts/lib/function";
+import * as O from "fp-ts/lib/Option";
+import I18n from "i18n-js";
+
+/**
+ * This function is used to get the text that will be read by the screen reader
+ * with the correct minus symbol pronunciation.
+ */
+export const getAccessibleAmountText = (amount?: string) =>
+ pipe(
+ amount,
+ O.fromNullable,
+ O.map(amount =>
+ amount.replace("-", I18n.t("global.accessibility.minusSymbol"))
+ ),
+ O.getOrElseW(() => undefined)
+ );
diff --git a/src/utils/object.ts b/src/utils/object.ts
new file mode 100644
index 00000000..7cf9fc84
--- /dev/null
+++ b/src/utils/object.ts
@@ -0,0 +1,12 @@
+import * as A from "fp-ts/Array";
+import * as O from "fp-ts/Option";
+import { pipe } from "fp-ts/lib/function";
+
+export const findFirstCaseInsensitive =
+ (obj: { [key: string]: T }) =>
+ (key: string): O.Option<[string, T]> =>
+ pipe(
+ obj,
+ Object.entries,
+ A.findFirst(([k, _]) => k.toLowerCase() === key.toLowerCase())
+ );
diff --git a/src/utils/url.ts b/src/utils/url.ts
new file mode 100644
index 00000000..b92424b8
--- /dev/null
+++ b/src/utils/url.ts
@@ -0,0 +1,9 @@
+import { ImageURISource } from "react-native";
+
+/**
+ * type guard to check if a value is an ImageURISource
+ * @argument value the value to check, can be anything
+ * @returns boolean
+ */
+export const isImageUri = (value: unknown): value is ImageURISource =>
+ typeof value === "object" && value !== null && "uri" in value;