diff --git a/ts/components/ui/BannerErrorState.tsx b/ts/components/ui/BannerErrorState.tsx index 62b081e6883..1cf9ad3ad74 100644 --- a/ts/components/ui/BannerErrorState.tsx +++ b/ts/components/ui/BannerErrorState.tsx @@ -67,7 +67,7 @@ export type BannerErrorStateProps = BaseBannerErrorStateProps & */ export const BannerErrorState = ({ viewRef, - icon, + icon = "warningFilled", label, actionText, onPress, diff --git a/ts/features/bonus/cgn/components/merchants/CgnMerchantsDiscountItem.tsx b/ts/features/bonus/cgn/components/merchants/CgnMerchantsDiscountItem.tsx index 40003d9990b..9246d3f5e9d 100644 --- a/ts/features/bonus/cgn/components/merchants/CgnMerchantsDiscountItem.tsx +++ b/ts/features/bonus/cgn/components/merchants/CgnMerchantsDiscountItem.tsx @@ -7,7 +7,7 @@ import CGN_ROUTES from "../../navigation/routes"; import { CgnDetailsParamsList } from "../../navigation/params"; import { useIODispatch } from "../../../../../store/hooks"; import { selectMerchantDiscount } from "../../store/actions/merchants"; -import { CgnModuleDiscount } from "./CgnModuleDiscount"; +import { ModuleCgnDiscount } from "./ModuleCgnDiscount"; type Props = { discount: Discount; @@ -38,7 +38,7 @@ export const CgnMerchantDiscountItem: React.FunctionComponent = ({ return ( - + ); }; diff --git a/ts/features/bonus/cgn/components/merchants/CgnModuleDiscount.tsx b/ts/features/bonus/cgn/components/merchants/ModuleCgnDiscount.tsx similarity index 66% rename from ts/features/bonus/cgn/components/merchants/CgnModuleDiscount.tsx rename to ts/features/bonus/cgn/components/merchants/ModuleCgnDiscount.tsx index 22bf2b746fb..06266d234ef 100644 --- a/ts/features/bonus/cgn/components/merchants/CgnModuleDiscount.tsx +++ b/ts/features/bonus/cgn/components/merchants/ModuleCgnDiscount.tsx @@ -1,14 +1,14 @@ import { Badge, H6, - HSpacer, + HStack, IOColors, IOModuleStyles, IOStyles, Icon, Tag, - VSpacer, - useIOExperimentalDesign, + VStack, + useIOTheme, useScaleAnimation } from "@pagopa/io-app-design-system"; import * as O from "fp-ts/lib/Option"; @@ -21,7 +21,7 @@ import I18n from "../../../../../i18n"; import { getCategorySpecs } from "../../utils/filters"; import { isValidDiscount, normalizedDiscountPercentage } from "./utils"; -type Props = { +export type ModuleCgnDiscount = { onPress: () => void; discount: Discount; }; @@ -39,6 +39,7 @@ const styles = StyleSheet.create({ borderWidth: 1 } }); + type CategoryTagProps = { category: ProductCategory; }; @@ -47,24 +48,19 @@ export const CategoryTag = ({ category }: CategoryTagProps) => { const categorySpecs = getCategorySpecs(category); return O.isSome(categorySpecs) ? ( - <> - - - - - - + ) : null; }; -export const CgnModuleDiscount = ({ onPress, discount }: Props) => { - const { isExperimental } = useIOExperimentalDesign(); + +export const ModuleCgnDiscount = ({ onPress, discount }: ModuleCgnDiscount) => { + const theme = useIOTheme(); const { onPressIn, onPressOut, scaleAnimatedStyle } = useScaleAnimation("medium"); @@ -92,37 +88,37 @@ export const CgnModuleDiscount = ({ onPress, discount }: Props) => { { alignItems: "center", justifyContent: "space-between" } ]} > - - - {discount.isNew && ( - <> + + {(discount.discount || discount.isNew) && ( + + {discount.isNew && ( - - - )} - {isValidDiscount(discount.discount) && ( - - )} - - + )} + {isValidDiscount(discount.discount) && ( + + )} + + )} +
{discount.name}
- - + {discount.productCategories.map(categoryKey => ( ))} - -
+ + diff --git a/ts/features/bonus/cgn/components/merchants/___tests___/CgnModuleDiscount.test.tsx b/ts/features/bonus/cgn/components/merchants/___tests___/ModuleCgnDiscount.test.tsx similarity index 87% rename from ts/features/bonus/cgn/components/merchants/___tests___/CgnModuleDiscount.test.tsx rename to ts/features/bonus/cgn/components/merchants/___tests___/ModuleCgnDiscount.test.tsx index d70d95b2cef..4411cb19ce2 100644 --- a/ts/features/bonus/cgn/components/merchants/___tests___/CgnModuleDiscount.test.tsx +++ b/ts/features/bonus/cgn/components/merchants/___tests___/ModuleCgnDiscount.test.tsx @@ -4,7 +4,7 @@ import React from "react"; import { Discount } from "../../../../../../../definitions/cgn/merchants/Discount"; import { ProductCategoryEnum } from "../../../../../../../definitions/cgn/merchants/ProductCategory"; import I18n from "../../../../../../i18n"; -import { CgnModuleDiscount } from "../CgnModuleDiscount"; +import { ModuleCgnDiscount } from "../ModuleCgnDiscount"; describe("CgnModuleDiscount", () => { const discount: Discount = { @@ -27,7 +27,7 @@ describe("CgnModuleDiscount", () => { it("should render correctly", () => { const { getByText } = render( - + ); expect(getByText(I18n.t("bonus.cgn.merchantsList.news"))).toBeTruthy(); @@ -37,7 +37,7 @@ describe("CgnModuleDiscount", () => { it("should call onPress when pressed", () => { const { getByRole } = render( - + ); fireEvent.press(getByRole("button")); expect(onPressMock).toHaveBeenCalled(); diff --git a/ts/features/bonus/cgn/components/merchants/discount/CgnDiscountHeader.tsx b/ts/features/bonus/cgn/components/merchants/discount/CgnDiscountHeader.tsx index 3174b6f1a96..1fa27a9b3f7 100644 --- a/ts/features/bonus/cgn/components/merchants/discount/CgnDiscountHeader.tsx +++ b/ts/features/bonus/cgn/components/merchants/discount/CgnDiscountHeader.tsx @@ -1,6 +1,7 @@ import { Badge, H3, + HStack, IOColors, IOStyles, VSpacer @@ -10,7 +11,7 @@ import React from "react"; import { StyleSheet, View } from "react-native"; import { Discount } from "../../../../../../../definitions/cgn/merchants/Discount"; import I18n from "../../../../../../i18n"; -import { CategoryTag } from "../CgnModuleDiscount"; +import { CategoryTag } from "../ModuleCgnDiscount"; import { isValidDiscount, normalizedDiscountPercentage } from "../utils"; type CgnDiscountHeaderProps = { @@ -65,11 +66,11 @@ export const CgnDiscountHeader = ({ )}

{name}

- + {productCategories.map(categoryKey => ( ))} - + ); diff --git a/ts/features/design-system/DesignSystem.tsx b/ts/features/design-system/DesignSystem.tsx index 55af8532d4a..ae7184e4a69 100644 --- a/ts/features/design-system/DesignSystem.tsx +++ b/ts/features/design-system/DesignSystem.tsx @@ -1,8 +1,8 @@ import { + BodySmall, Divider, - H2, + H3, IOVisualCostants, - BodySmall, ListItemNav, VSpacer, VStack, @@ -109,7 +109,9 @@ export const DesignSystem = () => { section: { title: string; description?: string }; }) => ( -

{title}

+

+ {title} +

{description && ( {description} diff --git a/ts/features/design-system/core/DSAdvice.tsx b/ts/features/design-system/core/DSAdvice.tsx index c5f090a79eb..81b6bc0f9b6 100644 --- a/ts/features/design-system/core/DSAdvice.tsx +++ b/ts/features/design-system/core/DSAdvice.tsx @@ -10,6 +10,7 @@ import * as React from "react"; import { Alert } from "react-native"; import { DSComponentViewerBox } from "../components/DSComponentViewerBox"; import { DesignSystemScreen } from "../components/DesignSystemScreen"; +import { BannerErrorState } from "../../../components/ui/BannerErrorState"; const onLinkPress = () => { Alert.alert("Alert", "Action triggered"); @@ -35,6 +36,11 @@ export const DSAdvice = () => { {renderFeatureInfo()}
+ +

BannerErrorState

+ {renderBannerErrorState()} +
+

Banner

{renderBanner()} @@ -44,6 +50,26 @@ export const DSAdvice = () => { ); }; +const renderBannerErrorState = () => ( + + + + + + + + +); + const renderFeatureInfo = () => ( diff --git a/ts/features/design-system/core/DSColors.tsx b/ts/features/design-system/core/DSColors.tsx index 141b3ffb784..bea71d79901 100644 --- a/ts/features/design-system/core/DSColors.tsx +++ b/ts/features/design-system/core/DSColors.tsx @@ -1,7 +1,7 @@ import { + BodySmall, H3, H6, - IOColorGradients, IOColors, IOColorsExtra, IOColorsLegacy, @@ -10,7 +10,6 @@ import { IOColorsTints, IOThemeDark, IOThemeLight, - BodySmall, VStack, hexToRgba, themeStatusColorsDarkMode, @@ -19,13 +18,11 @@ import { } from "@pagopa/io-app-design-system"; import * as React from "react"; import { ColorValue, Dimensions, StyleSheet, Text, View } from "react-native"; -import LinearGradient from "react-native-linear-gradient"; import { IOStyles } from "../../../components/core/variables/IOStyles"; import themeVariables from "../../../theme/variables"; import { DesignSystemScreen } from "../components/DesignSystemScreen"; const macroSectionMargin = 48; -const gradientItemGutter = 16; const sectionTitleMargin = 16; const colorItemGutter = 32; const colorItemPadding = 8; @@ -36,13 +33,6 @@ const colorItemBorderDarkMode = hexToRgba(IOColors.white, 0.25); const colorPillBg = hexToRgba(IOColors.black, 0.2); const styles = StyleSheet.create({ - gradientItemsWrapper: { - flexDirection: "row", - flexWrap: "wrap", - justifyContent: "flex-start", - marginLeft: (gradientItemGutter / 2) * -1, - marginRight: (gradientItemGutter / 2) * -1 - }, colorItemsWrapper: { flexDirection: "row", flexWrap: "wrap", @@ -93,12 +83,6 @@ const styles = StyleSheet.create({ borderTopRightRadius: 24, borderBottomRightRadius: 24 }, - gradientWrapper: { - width: "50%", - justifyContent: "flex-start", - paddingHorizontal: gradientItemGutter / 2, - marginBottom: 16 - }, colorItem: { width: "100%", padding: colorItemPadding, @@ -112,15 +96,6 @@ const styles = StyleSheet.create({ colorItemDarkMode: { borderColor: colorItemBorderDarkMode }, - gradientItem: { - aspectRatio: 2 / 1, - borderRadius: 8, - padding: 12, - alignItems: "flex-end", - justifyContent: "space-between", - borderColor: colorItemBorderLightMode, - borderWidth: 1 - }, colorPill: { overflow: "hidden", color: IOColors.white, @@ -285,17 +260,6 @@ export const DSColors = () => {
- {/* GRADIENTS */} - -

Gradients

- - - {Object.entries(IOColorGradients).map(([name, colorValues]) => ( - - ))} - -
- {/* LEGACY */} @@ -372,41 +336,6 @@ const ColorBox = ({ ); }; -type GradientBoxProps = { - name: string; - colors: Array; -}; - -const GradientBox = ({ name, colors }: GradientBoxProps) => { - const theme = useIOTheme(); - const [first, last] = colors; - - return ( - - - {first && {first}} - {last && {last}} - - {name && ( - - {name} - - )} - - ); -}; - type SmallCapsTitleProps = { title: string; darkMode?: boolean; diff --git a/ts/features/design-system/core/DSListItems.tsx b/ts/features/design-system/core/DSListItems.tsx index 57ba689ac01..637ee7ebc60 100644 --- a/ts/features/design-system/core/DSListItems.tsx +++ b/ts/features/design-system/core/DSListItems.tsx @@ -1,4 +1,3 @@ -import { NonEmptyString } from "@pagopa/ts-commons/lib/strings"; import * as React from "react"; import { @@ -17,14 +16,16 @@ import { useIOTheme } from "@pagopa/io-app-design-system"; import { Alert } from "react-native"; +import I18n from "../../../i18n"; import { DSComponentViewerBox } from "../components/DSComponentViewerBox"; -import { ProductCategoryEnum } from "../../../../definitions/cgn/merchants/ProductCategory"; -import { CgnMerchantDiscountItem } from "../../bonus/cgn/components/merchants/CgnMerchantsDiscountItem"; +import { ListItemMessage } from "../../messages/components/Home/DS/ListItemMessage"; +import { ListItemMessageSkeleton } from "../../messages/components/Home/DS/ListItemMessageSkeleton"; import { getBadgePropsByTransactionStatus } from "../../payments/common/utils"; -import { DesignSystemScreen } from "../components/DesignSystemScreen"; import { ListItemTransactionStatus } from "../../payments/common/utils/types"; +import { ListItemSearchInstitution } from "../../services/common/components/ListItemSearchInstitution"; +import { DesignSystemScreen } from "../components/DesignSystemScreen"; const onButtonPress = () => { Alert.alert("Alert", "Action triggered"); @@ -34,6 +35,8 @@ const onCopyButtonPress = () => { Alert.alert("Copied!", "Value copied"); }; +const cdnPath = "https://assets.cdn.io.pagopa.it/logos/organizations/"; + const sectionTitleMargin = 16; const sectionMargin = 48; const componentMargin = 32; @@ -49,6 +52,11 @@ export const DSListItems = () => { {renderListItemNav()} + +

ListItemMessage

+ {renderListItemMessage()} +
+

ListItemInfoCopy

{renderListItemInfoCopy()} @@ -70,31 +78,15 @@ export const DSListItems = () => {
-

ListItemTransaction

- {renderListItemTransaction()} +

+ ListItemSearchInstitution +

+ {renderListItemSearchInstitution()}
-

Specific

- - - - - +

ListItemTransaction

+ {renderListItemTransaction()}
@@ -160,6 +152,20 @@ const renderListItemNav = () => ( hideChevron /> + + + + + @@ -181,6 +187,99 @@ const renderListItemNav = () => ( ); +const listItemMessageSample: ListItemMessage = { + formattedDate: "09 dic", + isRead: false, + messageTitle: "Il tuo appuntamento", + organizationName: "Ministero dell'Interno", + serviceName: "Carta d'Identità Elettronica", + accessibilityLabel: "Leggi il messaggio inviato dal Ministero dell'Interno", + serviceLogos: [{ uri: `${cdnPath}80215430580.png` }], + onLongPress: () => { + Alert.alert("Long press"); + }, + onPress: () => { + Alert.alert("Pressed"); + } +}; + +const renderListItemMessage = () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + const renderListItemInfoCopy = () => ( ( ); +/* LIST ITEM SEARCH INSTITUTION */ + +const renderListItemSearchInstitution = () => ( + + + + + +); + /* LIST ITEM TRANSACTION */ /* Mock assets */ -const cdnPath = "https://assets.cdn.io.pagopa.it/logos/organizations/"; const organizationLogoURI = { imageSource: `${cdnPath}82003830161.png`, name: "Comune di Milano" diff --git a/ts/features/design-system/core/DSLogos.tsx b/ts/features/design-system/core/DSLogos.tsx index b6ba74d7e44..17275b21fe6 100644 --- a/ts/features/design-system/core/DSLogos.tsx +++ b/ts/features/design-system/core/DSLogos.tsx @@ -23,6 +23,7 @@ import { import * as React from "react"; import { ScrollView, StyleSheet, View } from "react-native"; import { LogoPaymentExtended } from "../../../components/ui/LogoPaymentExtended"; +import { AvatarDouble } from "../../messages/components/Home/DS/AvatarDouble"; import { DSComponentViewerBox } from "../components/DSComponentViewerBox"; import { DSLogoPaymentViewerBox, @@ -199,6 +200,12 @@ const renderAvatar = () => ( + + + + ); diff --git a/ts/features/design-system/core/DSModules.tsx b/ts/features/design-system/core/DSModules.tsx index 914d8edb82d..c111b29e006 100644 --- a/ts/features/design-system/core/DSModules.tsx +++ b/ts/features/design-system/core/DSModules.tsx @@ -11,9 +11,12 @@ import { VStack, useIOTheme } from "@pagopa/io-app-design-system"; +import { NonEmptyString } from "@pagopa/ts-commons/lib/strings"; import * as React from "react"; import { Alert, ImageSourcePropType } from "react-native"; +import { ProductCategoryEnum } from "../../../../definitions/cgn/merchants/ProductCategory"; import CgnLogo from "../../../../img/bonus/cgn/cgn_logo.png"; +import { ModuleCgnDiscount } from "../../bonus/cgn/components/merchants/ModuleCgnDiscount"; import { getBadgeTextByPaymentNoticeStatus } from "../../messages/utils/strings"; import { DSComponentViewerBox } from "../components/DSComponentViewerBox"; import { DesignSystemScreen } from "../components/DesignSystemScreen"; @@ -75,6 +78,11 @@ export const DSModules = () => { {renderModuleSummary()} + +

ModuleCgnDiscount

+ {renderModuleCgnDiscount()} +
+

ModuleIDP

{renderModuleIDP()} @@ -276,6 +284,52 @@ const renderModuleSummary = () => (
); +const mockModuleCgnDiscountData = { + name: "Small Rubber Chips" as NonEmptyString, + id: "28201" as NonEmptyString, + description: undefined, + discount: undefined, + discountUrl: "https://localhost", + endDate: new Date(), + isNew: false, + productCategories: [ProductCategoryEnum.cultureAndEntertainment], + startDate: new Date() +}; + +const renderModuleCgnDiscount = () => ( + + + + + + + + + + + +); + const mockIDPProviderItem = { id: "posteid", name: "Poste ID", diff --git a/ts/features/design-system/core/DSTypography.tsx b/ts/features/design-system/core/DSTypography.tsx index 5563710fdc8..5a92bf0a8c1 100644 --- a/ts/features/design-system/core/DSTypography.tsx +++ b/ts/features/design-system/core/DSTypography.tsx @@ -106,7 +106,7 @@ const H4Row = () => { const theme = useIOTheme(); return ( - +

Header H4

Header H4

{ const theme = useIOTheme(); return ( - +
Header H5
Header H5
Header H5
@@ -134,7 +134,7 @@ const H6Row = () => { const theme = useIOTheme(); return ( - +
Header H6
Header H6
Header H6
@@ -146,7 +146,7 @@ const ButtonTextRow = () => { const theme = useIOTheme(); return ( - + ButtonText ButtonText @@ -164,7 +164,7 @@ const CaptionRow = () => { const theme = useIOTheme(); return ( - + Caption Caption Caption @@ -222,7 +222,7 @@ export const BodySmallRow = () => { Body small SB asLink - + Body small Regular Body small Regular diff --git a/ts/features/messages/components/Home/DS/DoubleAvatar.tsx b/ts/features/messages/components/Home/DS/AvatarDouble.tsx similarity index 94% rename from ts/features/messages/components/Home/DS/DoubleAvatar.tsx rename to ts/features/messages/components/Home/DS/AvatarDouble.tsx index 3509fa4bcda..eb1275a5228 100644 --- a/ts/features/messages/components/Home/DS/DoubleAvatar.tsx +++ b/ts/features/messages/components/Home/DS/AvatarDouble.tsx @@ -17,7 +17,7 @@ import { } from "@pagopa/io-app-design-system"; import { addCacheTimestampToUri } from "../../../../../utils/image"; -type DoubleAvatarProps = { +type AvatarDoubleProps = { backgroundLogoUri?: | ImageRequireSource | ImageURISource @@ -74,12 +74,12 @@ const getImageState = ( }; /** - * DoubleAvatar component is used to display the background logo of an organization, with a fixed pagoPA icon on top. It accepts the following props: + * `AvatarDouble` component is used to display the background logo of an organization, with a fixed pagoPA icon on top. It accepts the following props: * - `backgroundLogoUri`: the uri of the image to display. If not provided, a placeholder icon will be displayed. It can be a single uri or an array of uris, in which case the first one that is available will be used. - * @param DoubleAvatarProps + * @param AvatarDoubleProps * @returns */ -export const DoubleAvatar = ({ backgroundLogoUri }: DoubleAvatarProps) => { +export const AvatarDouble = ({ backgroundLogoUri }: AvatarDoubleProps) => { const theme = useIOTheme(); const indexValue = React.useRef(0); diff --git a/ts/features/messages/components/Home/DS/CustomPressableListItemBase.tsx b/ts/features/messages/components/Home/DS/CustomPressableListItemBase.tsx deleted file mode 100644 index 82e72558d48..00000000000 --- a/ts/features/messages/components/Home/DS/CustomPressableListItemBase.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { - IOColors, - IOListItemStyles, - useListItemAnimation, - WithTestID -} from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { Pressable } from "react-native"; -import Animated from "react-native-reanimated"; - -export type PressableBaseProps = WithTestID< - Pick< - React.ComponentProps, - | "onPress" - | "onLongPress" - | "accessibilityLabel" - | "accessibilityHint" - | "accessibilityState" - | "accessibilityRole" - > & { minHeight?: number; selected?: boolean } ->; - -export const CustomPressableListItemBase = ({ - onPress, - onLongPress, - testID, - children, - accessibilityRole, - minHeight, - selected, - ...props -}: React.PropsWithChildren) => { - const { onPressIn, onPressOut, scaleAnimatedStyle, backgroundAnimatedStyle } = - useListItemAnimation(); - - return ( - - - - {children} - - - - ); -}; diff --git a/ts/features/messages/components/Home/DS/ListItemMessage.tsx b/ts/features/messages/components/Home/DS/ListItemMessage.tsx new file mode 100644 index 00000000000..fdc0941645a --- /dev/null +++ b/ts/features/messages/components/Home/DS/ListItemMessage.tsx @@ -0,0 +1,226 @@ +import { + AnimatedMessageCheckbox, + Avatar, + BodySmall, + H6, + HStack, + IOColors, + IOListItemStyles, + IOStyles, + IOVisualCostants, + Tag, + useIOTheme, + useListItemAnimation, + WithTestID +} from "@pagopa/io-app-design-system"; +import React, { ComponentProps } from "react"; +import { ImageURISource, Pressable, StyleSheet, View } from "react-native"; +import Animated from "react-native-reanimated"; +import Svg, { Circle } from "react-native-svg"; +import I18n from "../../../../../i18n"; +import { AvatarDouble } from "./AvatarDouble"; + +export const ListItemMessageStandardHeight = 95; +export const ListItemMessageEnhancedHeight = 133; + +const styles = StyleSheet.create({ + container: { + flexDirection: "row", + paddingHorizontal: 16 + }, + organizationContainer: { + flexDirection: "row" + }, + messageReadContainer: { + justifyContent: "center", + marginLeft: 8 + }, + serviceNameAndMessageTitleContainer: { + flexDirection: "row" + }, + serviceLogoAndSelectionContainer: { + justifyContent: "center" + }, + serviceLogoContainer: { + alignItems: "center", + justifyContent: "center", + height: IOVisualCostants.avatarSizeSmall, + width: IOVisualCostants.avatarSizeSmall + }, + textContainer: { ...IOStyles.flex, marginLeft: 8 } +}); + +type ListItemMessageTag = { + text: string; + variant: Extract; +}; + +export type ListItemMessage = WithTestID<{ + accessibilityLabel: string; + tag?: ListItemMessageTag; + avatarDouble?: boolean; + formattedDate: string; + isRead: boolean; + messageTitle: string; + onLongPress: () => void; + onPress: () => void; + organizationName: string; + selected?: boolean; + serviceLogos?: ReadonlyArray; + serviceName: string; +}> & + Pick< + ComponentProps, + | "onPress" + | "onLongPress" + | "accessibilityLabel" + | "accessibilityHint" + | "accessibilityState" + | "accessibilityRole" + >; + +type UnreadBadgeProps = { + color?: IOColors; + width?: number; +}; + +const UnreadBadge = ({ color, width = 14 }: UnreadBadgeProps) => ( + + + +); + +export const ListItemMessage = ({ + accessibilityLabel, + accessibilityRole, + tag, + avatarDouble, + formattedDate, + isRead, + messageTitle, + onLongPress, + onPress, + organizationName, + serviceName, + selected, + serviceLogos, + testID +}: ListItemMessage) => { + const theme = useIOTheme(); + + const { onPressIn, onPressOut, scaleAnimatedStyle, backgroundAnimatedStyle } = + useListItemAnimation(); + + return ( + + + + + + + {avatarDouble ? ( + + ) : ( + + )} + + + + + + + + {/* We use 'flexGrow: 1, flexShrink: 1' instead of 'flex: 1' + in order to keep a consistent styling approach between + react and react-native (on react-native, 'flex: 1' does + something similar to 'flex-grow') */} +
+ {organizationName} +
+ + {formattedDate} + +
+ + + {`${serviceName} · `} + {messageTitle} + + {!isRead && ( + + + + )} + + {tag && ( + /* We add a `View` because we need to re-arrange block elements using + `VStack` and/or `HStack` */ + + + + {tag.variant === "legalMessage" && ( + + )} + + + )} +
+
+
+
+
+ ); +}; diff --git a/ts/features/messages/components/Home/DS/MessageListItemSkeleton.tsx b/ts/features/messages/components/Home/DS/ListItemMessageSkeleton.tsx similarity index 89% rename from ts/features/messages/components/Home/DS/MessageListItemSkeleton.tsx rename to ts/features/messages/components/Home/DS/ListItemMessageSkeleton.tsx index 13f376cdf39..b91e632a14e 100644 --- a/ts/features/messages/components/Home/DS/MessageListItemSkeleton.tsx +++ b/ts/features/messages/components/Home/DS/ListItemMessageSkeleton.tsx @@ -1,14 +1,16 @@ -import React from "react"; -import { StyleSheet, View } from "react-native"; -import Placeholder from "rn-placeholder"; import { IOColors, IOStyles, IOVisualCostants, WithTestID } from "@pagopa/io-app-design-system"; +import React from "react"; +import { StyleSheet, View } from "react-native"; +import Placeholder from "rn-placeholder"; +import { ListItemMessageStandardHeight } from "./ListItemMessage"; -export const SkeletonHeight = 95 + StyleSheet.hairlineWidth; +export const SkeletonHeight = + ListItemMessageStandardHeight + StyleSheet.hairlineWidth; const styles = StyleSheet.create({ container: { @@ -39,13 +41,13 @@ const styles = StyleSheet.create({ } }); -type MessageListItemSkeletonProps = WithTestID<{ +type ListItemMessageSkeletonProps = WithTestID<{ accessibilityLabel: string; }>; -export const MessageListItemSkeleton = ({ +export const ListItemMessageSkeleton = ({ accessibilityLabel -}: MessageListItemSkeletonProps) => ( +}: ListItemMessageSkeletonProps) => ( void; - onPress: () => void; - organizationName: string; - selected?: boolean; - serviceLogos?: ReadonlyArray; - serviceName: string; -}>; - -type BadgeComponentProps = { - color?: IOColors; - width?: number; -}; - -const BadgeComponent = ({ color, width = 14 }: BadgeComponentProps) => ( - - - -); - -export const MessageListItem = ({ - accessibilityLabel, - badgeText, - badgeVariant, - doubleAvatar, - formattedDate, - isRead, - messageTitle, - onLongPress, - onPress, - organizationName, - serviceName, - selected, - serviceLogos, - testID -}: MessageListItemProps) => { - const theme = useIOTheme(); - - return ( - - - - - {doubleAvatar ? ( - - ) : ( - - )} - - - - - - - - {/* We use 'flexGrow: 1, flexShrink: 1' instead of 'flex: 1' - in order to keep a consistent styling approach between - react and react-native (on react-native, 'flex: 1' does - something similar to 'flex-grow') */} -
- {organizationName} -
- - {formattedDate} - -
- - - {`${serviceName} · `} - {messageTitle} - - {!isRead && ( - - - - )} - - {badgeText && badgeVariant && ( - - - {badgeVariant === "legalMessage" && ( - <> - - - - )} - - )} -
-
-
- ); -}; diff --git a/ts/features/messages/components/Home/Footer.tsx b/ts/features/messages/components/Home/Footer.tsx index 2d3d5000013..4bae8e24178 100644 --- a/ts/features/messages/components/Home/Footer.tsx +++ b/ts/features/messages/components/Home/Footer.tsx @@ -1,9 +1,9 @@ import React from "react"; +import I18n from "../../../../i18n"; import { useIOSelector } from "../../../../store/hooks"; import { shouldShowFooterListComponentSelector } from "../../store/reducers/allPaginated"; import { MessageListCategory } from "../../types/messageListCategory"; -import I18n from "../../../../i18n"; -import { MessageListItemSkeleton } from "./DS/MessageListItemSkeleton"; +import { ListItemMessageSkeleton } from "./DS/ListItemMessageSkeleton"; type FooterProps = { category: MessageListCategory; @@ -17,6 +17,6 @@ export const Footer = ({ category }: FooterProps) => { return null; } return ( - + ); }; diff --git a/ts/features/messages/components/Home/MessageList.tsx b/ts/features/messages/components/Home/MessageList.tsx index 4230643c2c5..7f7f012a8ab 100644 --- a/ts/features/messages/components/Home/MessageList.tsx +++ b/ts/features/messages/components/Home/MessageList.tsx @@ -20,9 +20,9 @@ import { import { UIMessage } from "../../types"; import { MessageListCategory } from "../../types/messageListCategory"; import { - MessageListItemSkeleton, + ListItemMessageSkeleton, SkeletonHeight -} from "./DS/MessageListItemSkeleton"; +} from "./DS/ListItemMessageSkeleton"; import { EmptyList } from "./EmptyList"; import { Footer } from "./Footer"; import { @@ -32,7 +32,7 @@ import { LayoutInfo, trackMessageListEndReachedIfAllowed } from "./homeUtils"; -import { WrappedMessageListItem } from "./WrappedMessageListItem"; +import { WrappedListItemMessage } from "./WrappedListItemMessage"; const styles = StyleSheet.create({ contentContainer: { @@ -127,13 +127,13 @@ export const MessageList = React.forwardRef( renderItem={({ index, item }) => { if (typeof item === "number") { return ( - ); } else { return ( - { +}: WrappedListItemMessage) => { const dispatch = useIODispatch(); const navigation = useIONavigation(); const store = useIOStore(); @@ -59,7 +59,7 @@ export const WrappedMessageListItem = ({ ); const messageCategoryTag = message.category.tag; - const doubleAvatar = messageCategoryTag === PaymentTagEnum.PAYMENT; + const avatarDouble = messageCategoryTag === PaymentTagEnum.PAYMENT; const serviceLogoUriSources = useMemo( () => logoForService(serviceId, organizationFiscalCode), [serviceId, organizationFiscalCode] @@ -73,19 +73,22 @@ export const WrappedMessageListItem = ({ message.createdAt, I18n.t("messages.yesterday") ); + const isRead = message.isRead; - const badgeText = - messageCategoryTag === SENDTagEnum.PN - ? I18n.t("features.pn.details.badge.legalValue") - : isPaymentMessageWithPaidNotice - ? I18n.t("messages.badge.paid") - : undefined; - const badgeVariant = + + const tag: ListItemMessage["tag"] = messageCategoryTag === SENDTagEnum.PN - ? "legalMessage" + ? { + variant: "legalMessage", + text: I18n.t("features.pn.details.badge.legalValue") + } : isPaymentMessageWithPaidNotice - ? "success" + ? { + variant: "success", + text: I18n.t("messages.badge.paid") + } : undefined; + const accessibilityLabel = useMemo( () => accessibilityLabelForMessageItem(message, isSelected), [isSelected, message] @@ -157,11 +160,10 @@ export const WrappedMessageListItem = ({ ]); return ( - source === "ARCHIVE" || isInboxSource(source); -export const isInboxSource = (source: WrappedMessageListItemProps["source"]) => +export const isInboxSource = (source: WrappedListItemMessage["source"]) => source === "INBOX"; -export const isSearchSource = (source: WrappedMessageListItemProps["source"]) => +export const isSearchSource = (source: WrappedListItemMessage["source"]) => source === "SEARCH"; diff --git a/ts/features/messages/components/Home/__tests__/WrappedMessageListItem.test.tsx b/ts/features/messages/components/Home/__tests__/WrappedListItemMessage.test.tsx similarity index 97% rename from ts/features/messages/components/Home/__tests__/WrappedMessageListItem.test.tsx rename to ts/features/messages/components/Home/__tests__/WrappedListItemMessage.test.tsx index 311976303c7..e157e55e211 100644 --- a/ts/features/messages/components/Home/__tests__/WrappedMessageListItem.test.tsx +++ b/ts/features/messages/components/Home/__tests__/WrappedListItemMessage.test.tsx @@ -10,7 +10,7 @@ import { UIMessage } from "../../../types"; import { MESSAGES_ROUTES } from "../../../navigation/routes"; import { TagEnum as SENDTagEnum } from "../../../../../../definitions/backend/MessageCategoryPN"; import { TagEnum as PaymentTagEnum } from "../../../../../../definitions/backend/MessageCategoryPayment"; -import { WrappedMessageListItem } from "../WrappedMessageListItem"; +import { WrappedListItemMessage } from "../WrappedListItemMessage"; import { TagEnum } from "../../../../../../definitions/backend/MessageCategoryBase"; import { GlobalState } from "../../../../../store/reducers/types"; import { @@ -34,7 +34,7 @@ jest.mock("react-redux", () => ({ useDispatch: () => mockDispatch })); -describe("WrappedMessageListItem", () => { +describe("WrappedListItemMessage", () => { beforeEach(() => { jest.resetAllMocks(); jest.clearAllMocks(); @@ -172,7 +172,7 @@ const renderComponent = ( } as GlobalState; const store = createStore(appReducer, stateWithPayment as any); return renderScreenWithNavigationStoreContext( - () => , + () => , MESSAGES_ROUTES.MESSAGES_HOME, {}, store diff --git a/ts/features/messages/components/Home/__tests__/__snapshots__/WrappedMessageListItem.test.tsx.snap b/ts/features/messages/components/Home/__tests__/__snapshots__/WrappedListItemMessage.test.tsx.snap similarity index 87% rename from ts/features/messages/components/Home/__tests__/__snapshots__/WrappedMessageListItem.test.tsx.snap rename to ts/features/messages/components/Home/__tests__/__snapshots__/WrappedListItemMessage.test.tsx.snap index 71da455b253..8ce8be171c4 100644 --- a/ts/features/messages/components/Home/__tests__/__snapshots__/WrappedMessageListItem.test.tsx.snap +++ b/ts/features/messages/components/Home/__tests__/__snapshots__/WrappedListItemMessage.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`WrappedMessageListItem should match snapshot, from SEND, read message 1`] = ` +exports[`WrappedListItemMessage should match snapshot, from SEND, read message 1`] = ` @@ -720,229 +716,231 @@ exports[`WrappedMessageListItem should match snapshot, from SEND, read message 1 - - - - - + > + + + + + + + Legal value + - - Legal value - - - - - - - - - - + > + + + + @@ -964,7 +962,7 @@ exports[`WrappedMessageListItem should match snapshot, from SEND, read message 1 `; -exports[`WrappedMessageListItem should match snapshot, from SEND, unread message 1`] = ` +exports[`WrappedListItemMessage should match snapshot, from SEND, unread message 1`] = ` @@ -1739,229 +1733,231 @@ exports[`WrappedMessageListItem should match snapshot, from SEND, unread message - - - - - + > + + + + + + + Legal value + - - Legal value - - - - - - - - - - + > + + + + @@ -1983,7 +1979,7 @@ exports[`WrappedMessageListItem should match snapshot, from SEND, unread message `; -exports[`WrappedMessageListItem should match snapshot, not from SEND, contains paid payment, read message 1`] = ` +exports[`WrappedListItemMessage should match snapshot, not from SEND, contains paid payment, read message 1`] = ` @@ -2785,175 +2777,184 @@ exports[`WrappedMessageListItem should match snapshot, not from SEND, contains p dynamicTypeRamp="footnote" maxFontSizeMultiplier={1.25} style={ - [ - {}, - { - "color": "#555C70", - "fontFamily": "Titillium Sans Pro", - "fontSize": 14, - "fontStyle": "normal", - "fontWeight": "600", - "lineHeight": 21, - }, - ] - } - > - Service name · - - - Message title - - - - - - - - - - - - - + > + Service name · + + Message title + + + + + + - Paid - + + + + + + + + + + Paid + + @@ -2974,7 +2975,7 @@ exports[`WrappedMessageListItem should match snapshot, not from SEND, contains p `; -exports[`WrappedMessageListItem should match snapshot, not from SEND, contains paid payment, unread message 1`] = ` +exports[`WrappedListItemMessage should match snapshot, not from SEND, contains paid payment, unread message 1`] = ` @@ -3871,135 +3868,144 @@ exports[`WrappedMessageListItem should match snapshot, not from SEND, contains p - - - - - - - - + + + + + - Paid - + "width": 6, + } + } + /> + + Paid + + @@ -4020,7 +4026,7 @@ exports[`WrappedMessageListItem should match snapshot, not from SEND, contains p `; -exports[`WrappedMessageListItem should match snapshot, not from SEND, contains unpaid payment, read message 1`] = ` +exports[`WrappedListItemMessage should match snapshot, not from SEND, contains unpaid payment, read message 1`] = ` @@ -4877,7 +4879,7 @@ exports[`WrappedMessageListItem should match snapshot, not from SEND, contains u `; -exports[`WrappedMessageListItem should match snapshot, not from SEND, contains unpaid payment, unread message 1`] = ` +exports[`WrappedListItemMessage should match snapshot, not from SEND, contains unpaid payment, unread message 1`] = ` @@ -5789,7 +5787,7 @@ exports[`WrappedMessageListItem should match snapshot, not from SEND, contains u `; -exports[`WrappedMessageListItem should match snapshot, not from SEND, not a payment, read message 1`] = ` +exports[`WrappedListItemMessage should match snapshot, not from SEND, not a payment, read message 1`] = ` @@ -6524,7 +6518,7 @@ exports[`WrappedMessageListItem should match snapshot, not from SEND, not a paym `; -exports[`WrappedMessageListItem should match snapshot, not from SEND, not a payment, unread message 1`] = ` +exports[`WrappedListItemMessage should match snapshot, not from SEND, not a payment, unread message 1`] = ` diff --git a/ts/features/messages/components/Home/homeUtils.ts b/ts/features/messages/components/Home/homeUtils.ts index 95f3d90160b..f3b4539925a 100644 --- a/ts/features/messages/components/Home/homeUtils.ts +++ b/ts/features/messages/components/Home/homeUtils.ts @@ -31,8 +31,11 @@ import { TagEnum } from "../../../../../definitions/backend/MessageCategoryPN"; import NavigationService from "../../../../navigation/NavigationService"; import { trackMessageListEndReached, trackMessagesPage } from "../../analytics"; import { MESSAGES_ROUTES } from "../../navigation/routes"; -import { EnhancedHeight, StandardHeight } from "./DS/MessageListItem"; -import { SkeletonHeight } from "./DS/MessageListItemSkeleton"; +import { + ListItemMessageEnhancedHeight, + ListItemMessageStandardHeight +} from "./DS/ListItemMessage"; +import { SkeletonHeight } from "./DS/ListItemMessageSkeleton"; export type LayoutInfo = { index: number; @@ -247,7 +250,9 @@ export const generateMessageListLayoutInfo = ( isPaymentMessageWithPaidNoticeSelector(state, message.category); const itemLayoutInfo: LayoutInfo = { index: i, - length: messageHasBadge ? EnhancedHeight : StandardHeight, + length: messageHasBadge + ? ListItemMessageEnhancedHeight + : ListItemMessageStandardHeight, offset: i > 0 ? messageListLayoutInfo[i - 1].offset + diff --git a/ts/features/messages/components/MessageDetail/OrganizationHeader.tsx b/ts/features/messages/components/MessageDetail/OrganizationHeader.tsx index 44d59ab6921..acb02120b2b 100644 --- a/ts/features/messages/components/MessageDetail/OrganizationHeader.tsx +++ b/ts/features/messages/components/MessageDetail/OrganizationHeader.tsx @@ -14,7 +14,7 @@ import { useIOSelector } from "../../../../store/hooks"; import { SERVICES_ROUTES } from "../../../services/common/navigation/routes"; import { messagePaymentDataSelector } from "../../store/reducers/detailsById"; import { UIMessageId } from "../../types"; -import { DoubleAvatar } from "../Home/DS/DoubleAvatar"; +import { AvatarDouble } from "../Home/DS/AvatarDouble"; export type OrganizationHeaderProps = { messageId: UIMessageId; @@ -77,7 +77,7 @@ export const OrganizationHeader = ({ {paymentData ? ( - + ) : ( )} diff --git a/ts/features/messages/screens/MessagesSearchScreen.tsx b/ts/features/messages/screens/MessagesSearchScreen.tsx index 1830f7e5da6..17c1e152fa9 100644 --- a/ts/features/messages/screens/MessagesSearchScreen.tsx +++ b/ts/features/messages/screens/MessagesSearchScreen.tsx @@ -27,7 +27,7 @@ import { EmptyList } from "../components/Search/EmptyList"; import { useIONavigation } from "../../../navigation/params/AppParamsList"; import { useIOStore } from "../../../store/hooks"; import { UIMessage } from "../types"; -import { WrappedMessageListItem } from "../components/Home/WrappedMessageListItem"; +import { WrappedListItemMessage } from "../components/Home/WrappedListItemMessage"; import { trackMessageSearchClosing, trackMessageSearchPage @@ -58,7 +58,7 @@ export const MessagesSearchScreen = () => { const renderItemCallback = useCallback( (itemInfo: ListRenderItemInfo) => ( - { + }: ListItemSearchInstitution) => { const { isExperimental } = useIOExperimentalDesign(); const theme = useIOTheme();