diff --git a/ts/components/ui/FooterActions.tsx b/ts/components/ui/FooterActions.tsx
index ac5f907bded..876a4454998 100644
--- a/ts/components/ui/FooterActions.tsx
+++ b/ts/components/ui/FooterActions.tsx
@@ -13,13 +13,7 @@ import {
useIOTheme
} from "@pagopa/io-app-design-system";
import * as React from "react";
-import {
- ComponentProps,
- Fragment,
- PropsWithChildren,
- useMemo,
- useState
-} from "react";
+import { ComponentProps, Fragment, PropsWithChildren, useState } from "react";
import {
ColorValue,
LayoutChangeEvent,
@@ -143,10 +137,10 @@ export const FooterActions = ({
const theme = useIOTheme();
const { isExperimental } = useIOExperimentalDesign();
- const type = actions?.type;
- const primaryAction = actions?.primary;
- const secondaryAction = actions?.secondary;
- const tertiaryAction = actions?.tertiary;
+ const { bottomMargin, extraBottomMargin } = useBottomMargins(
+ actions,
+ excludeSafeAreaMargins
+ );
/* Total height of actions */
const [actionBlockHeight, setActionBlockHeight] =
@@ -158,41 +152,15 @@ export const FooterActions = ({
const TRANSPARENT_BG_COLOR: ColorValue = "transparent";
const BUTTONSOLID_HEIGHT = isExperimental ? buttonSolidHeight : 40;
- const insets = useSafeAreaInsets();
- const needSafeAreaMargin = useMemo(() => insets.bottom !== 0, [insets]);
- const safeAreaMargin = useMemo(() => insets.bottom, [insets]);
-
- /* Check if the iPhone bottom handle is present.
- If not, or if you don't need safe area insets,
- add a default margin to prevent the button
- from sticking to the bottom. */
- const bottomMargin: number = useMemo(
- () =>
- !needSafeAreaMargin || excludeSafeAreaMargins
- ? IOVisualCostants.appMarginDefault
- : safeAreaMargin,
- [needSafeAreaMargin, excludeSafeAreaMargins, safeAreaMargin]
- );
-
- /* When the secondary action is visible, add extra margin
- to avoid little space from iPhone bottom handle */
- const extraBottomMargin: number = useMemo(
- () => (secondaryAction && needSafeAreaMargin ? extraSafeAreaMargin : 0),
- [needSafeAreaMargin, secondaryAction]
- );
-
/* Safe background block. Cover everything until it reaches
the half of the primary action button. It avoids
glitchy behavior underneath. */
- const safeBackgroundBlockHeight: number = useMemo(
- () => bottomMargin + actionBlockHeight - BUTTONSOLID_HEIGHT / 2,
- [BUTTONSOLID_HEIGHT, actionBlockHeight, bottomMargin]
- );
+ const safeBackgroundBlockHeight =
+ bottomMargin + actionBlockHeight - BUTTONSOLID_HEIGHT / 2;
const getActionBlockMeasurements = (event: LayoutChangeEvent) => {
const { height } = event.nativeEvent.layout;
setActionBlockHeight(height);
-
/* Height of the safe bottom area, applied to the ScrollView:
Actions + Content end margin */
const safeBottomAreaHeight = bottomMargin + height + contentEndMargin;
@@ -247,48 +215,76 @@ export const FooterActions = ({
{`Height: ${actionBlockHeight}`}
)}
- {primaryAction && }
+ {renderActions(actions, extraBottomMargin)}
+
+
+ );
+};
+
+const useBottomMargins = (
+ actions: FooterActions | undefined,
+ excludeSafeAreaMargins: boolean
+) => {
+ const insets = useSafeAreaInsets();
+ const needSafeAreaMargin = insets.bottom !== 0;
- {type === "TwoButtons" && (
-
-
- {secondaryAction && (
- )}
- />
- )}
-
- )}
+ /* Check if the iPhone bottom handle is present.
+ If not, or if you don't need safe area insets,
+ add a default margin to prevent the button
+ from sticking to the bottom. */
+ const bottomMargin =
+ !needSafeAreaMargin || excludeSafeAreaMargins
+ ? IOVisualCostants.appMarginDefault
+ : insets.bottom;
- {type === "ThreeButtons" && (
-
- {secondaryAction && (
-
-
-
-
- )}
+ /* When the secondary action is visible, add extra margin
+ to avoid little space from iPhone bottom handle */
+ const extraBottomMargin =
+ actions?.secondary && needSafeAreaMargin ? extraSafeAreaMargin : 0;
- {tertiaryAction && (
-
-
-
-
- )}
-
- )}
-
-
+ return { bottomMargin, extraBottomMargin };
+};
+
+const renderActions = (
+ actions: FooterActions | undefined,
+ extraBottomMargin: number
+) => {
+ if (!actions) {
+ return null;
+ }
+ const {
+ type,
+ primary: primaryAction,
+ secondary: secondaryAction,
+ tertiary: tertiaryAction
+ } = actions;
+ return (
+
+ {primaryAction && }
+ {type === "TwoButtons" && secondaryAction && (
+
+
+
+
+ )}
+ {type === "ThreeButtons" && (
+ <>
+ {secondaryAction && (
+ <>
+
+
+ >
+ )}
+ {tertiaryAction && (
+
+
+
+
+ )}
+ >
+ )}
+
);
};
diff --git a/ts/components/ui/IOScrollView.tsx b/ts/components/ui/IOScrollView.tsx
index 245d9024fe1..0a3922e169f 100644
--- a/ts/components/ui/IOScrollView.tsx
+++ b/ts/components/ui/IOScrollView.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable functional/immutable-data */
import {
ButtonLink,
ButtonOutline,
@@ -17,7 +18,6 @@ import {
Fragment,
PropsWithChildren,
useLayoutEffect,
- useMemo,
useState
} from "react";
import {
@@ -128,11 +128,6 @@ export const IOScrollView = ({
}: IOScrollView) => {
const theme = useIOTheme();
- const type = actions?.type;
- const primaryAction = actions?.primary;
- const secondaryAction = actions?.secondary;
- const tertiaryAction = actions?.tertiary;
-
/* Navigation */
const navigation = useNavigation();
@@ -151,20 +146,16 @@ export const IOScrollView = ({
};
const insets = useSafeAreaInsets();
- const needSafeAreaMargin = useMemo(() => insets.bottom !== 0, [insets]);
- const safeAreaMargin = useMemo(() => insets.bottom, [insets]);
+ const needSafeAreaMargin = insets.bottom !== 0;
/* Check if the iPhone bottom handle is present.
If not, or if you don't need safe area insets,
add a default margin to prevent the button
from sticking to the bottom. */
- const bottomMargin: number = useMemo(
- () =>
- !needSafeAreaMargin || excludeSafeAreaMargins
- ? IOVisualCostants.appMarginDefault
- : safeAreaMargin,
- [needSafeAreaMargin, excludeSafeAreaMargins, safeAreaMargin]
- );
+ const bottomMargin =
+ !needSafeAreaMargin || excludeSafeAreaMargins
+ ? IOVisualCostants.appMarginDefault
+ : insets.bottom;
/* GENERATE EASING GRADIENT
Background color should be app main background
@@ -182,30 +173,21 @@ export const IOScrollView = ({
/* When the secondary action is visible, add extra margin
to avoid little space from iPhone bottom handle */
- const extraBottomMargin: number = useMemo(
- () => (secondaryAction && needSafeAreaMargin ? extraSafeAreaMargin : 0),
- [needSafeAreaMargin, secondaryAction]
- );
+ const extraBottomMargin =
+ actions?.secondary && needSafeAreaMargin ? extraSafeAreaMargin : 0;
/* Safe background block. Cover at least 85% of the space
to avoid glitchy elements underneath */
- const safeBackgroundBlockHeight: number = useMemo(
- () => (bottomMargin + actionBlockHeight) * 0.85,
- [actionBlockHeight, bottomMargin]
- );
+ const safeBackgroundBlockHeight = (bottomMargin + actionBlockHeight) * 0.85;
/* Total height of "Actions + Gradient" area */
- const gradientAreaHeight: number = useMemo(
- () => bottomMargin + actionBlockHeight + gradientSafeAreaHeight,
- [actionBlockHeight, bottomMargin]
- );
+ const gradientAreaHeight =
+ bottomMargin + actionBlockHeight + gradientSafeAreaHeight;
/* Height of the safe bottom area, applied to the ScrollView:
Actions + Content end margin */
- const safeBottomAreaHeight: number = useMemo(
- () => bottomMargin + actionBlockHeight + contentEndMargin,
- [actionBlockHeight, bottomMargin]
- );
+ const safeBottomAreaHeight =
+ bottomMargin + actionBlockHeight + contentEndMargin;
const handleScroll = useAnimatedScrollHandler(
({ contentOffset, layoutMeasurement, contentSize }) => {
@@ -213,9 +195,7 @@ export const IOScrollView = ({
const maxScrollHeight = contentSize.height - layoutMeasurement.height;
const scrollPercentage = scrollPosition / maxScrollHeight;
- // eslint-disable-next-line functional/immutable-data
scrollPositionAbsolute.value = scrollPosition;
- // eslint-disable-next-line functional/immutable-data
scrollPositionPercentage.value = scrollPercentage;
}
);
@@ -232,15 +212,12 @@ export const IOScrollView = ({
/* Set custom header with `react-navigation` library using
`useLayoutEffect` hook */
- const scrollValues: IOSCrollViewHeaderScrollValues = useMemo(
- () => ({
+ useLayoutEffect(() => {
+ const scrollValues: IOSCrollViewHeaderScrollValues = {
contentOffsetY: scrollPositionAbsolute,
triggerOffset: snapOffset || 0
- }),
- [scrollPositionAbsolute, snapOffset]
- );
+ };
- useLayoutEffect(() => {
if (headerConfig) {
navigation.setOptions({
header: () => (
@@ -249,7 +226,7 @@ export const IOScrollView = ({
headerTransparent: headerConfig.transparent
});
}
- }, [headerConfig, navigation, scrollValues]);
+ }, [headerConfig, navigation, scrollPositionAbsolute, snapOffset]);
return (
@@ -314,8 +291,8 @@ export const IOScrollView = ({
{/* Safe background block. It's added because when you swipe up
- quickly, the content below is visible for about 100ms. Without this
- block, the content appears glitchy. */}
+ quickly, the content below is visible for about 100ms. Without this
+ block, the content appears glitchy. */}
-
- {primaryAction && }
+ {renderActionButtons(actions, extraBottomMargin)}
+
+
+ )}
+
+ );
+};
- {type === "TwoButtons" && (
-
-
- {secondaryAction && (
- )}
- />
- )}
-
- )}
+const renderActionButtons = (
+ actions: IOScrollViewActions,
+ extraBottomMargin: number
+) => {
+ const {
+ type,
+ primary: primaryAction,
+ secondary: secondaryAction,
+ tertiary: tertiaryAction
+ } = actions;
- {type === "ThreeButtons" && (
-
- {secondaryAction && (
-
-
-
-
- )}
+ return (
+ <>
+ {primaryAction && }
- {tertiaryAction && (
-
-
-
-
- )}
-
- )}
-
+ {type === "TwoButtons" && (
+
+
+ )}
+ />
)}
-
+
+ {type === "ThreeButtons" && (
+
+
+
+
+
+
+
+
+
+ )}
+ >
);
};
diff --git a/ts/features/design-system/DesignSystem.tsx b/ts/features/design-system/DesignSystem.tsx
index 5f42f5a9132..478def23625 100644
--- a/ts/features/design-system/DesignSystem.tsx
+++ b/ts/features/design-system/DesignSystem.tsx
@@ -1,16 +1,17 @@
-import { SectionList, StatusBar, View, useColorScheme } from "react-native";
-import * as React from "react";
import {
- useIOTheme,
Divider,
- VSpacer,
+ IOVisualCostants,
ListItemNav,
- IOVisualCostants
+ VSpacer,
+ useIOTheme
} from "@pagopa/io-app-design-system";
-import { IOStyles } from "../../components/core/variables/IOStyles";
-import { useIONavigation } from "../../navigation/params/AppParamsList";
+import * as React from "react";
+import { SectionList, StatusBar, View, useColorScheme } from "react-native";
import { H1 } from "../../components/core/typography/H1";
import { LabelSmall } from "../../components/core/typography/LabelSmall";
+import { IOStyles } from "../../components/core/variables/IOStyles";
+import { useScreenEndMargin } from "../../hooks/useScreenEndMargin";
+import { useIONavigation } from "../../navigation/params/AppParamsList";
import DESIGN_SYSTEM_ROUTES from "./navigation/routes";
type SingleSectionProps = {
@@ -40,7 +41,13 @@ const DATA_ROUTES_LEGACY: RoutesProps = Object.values(
DESIGN_SYSTEM_ROUTES.LEGACY
);
-const DESIGN_SYSTEM_SECTION_DATA = [
+type SectionDataProps = {
+ title: string;
+ description?: string;
+ data: RoutesProps;
+};
+
+const DESIGN_SYSTEM_SECTION_DATA: Array = [
{
title: "Foundation",
data: DATA_ROUTES_FOUNDATION
@@ -74,6 +81,8 @@ export const DesignSystem = () => {
const colorScheme = useColorScheme();
const navigation = useIONavigation();
+ const { screenEndMargin } = useScreenEndMargin();
+
const renderDSNavItem = ({
item: { title, route }
}: {
@@ -101,7 +110,13 @@ export const DesignSystem = () => {
);
- const renderDSSectionFooter = () => ;
+ const renderDSSectionFooter = ({ section }: { section: SectionDataProps }) =>
+ /* We exclude the last section because
+ we already apply the `screenEndMargin` */
+ DESIGN_SYSTEM_SECTION_DATA.indexOf(section) !==
+ DESIGN_SYSTEM_SECTION_DATA.length - 1 ? (
+
+ ) : null;
return (
<>
@@ -115,7 +130,8 @@ export const DesignSystem = () => {
contentContainerStyle={[
IOStyles.horizontalContentPadding,
{
- paddingTop: IOVisualCostants.appMarginDefault
+ paddingTop: IOVisualCostants.appMarginDefault,
+ paddingBottom: screenEndMargin
}
]}
renderSectionHeader={renderDSSection}
diff --git a/ts/features/design-system/components/DesignSystemScreen.tsx b/ts/features/design-system/components/DesignSystemScreen.tsx
index fe02f4e5038..c76b3966a51 100644
--- a/ts/features/design-system/components/DesignSystemScreen.tsx
+++ b/ts/features/design-system/components/DesignSystemScreen.tsx
@@ -1,11 +1,11 @@
-import * as React from "react";
-import { ScrollView, StatusBar, View, useColorScheme } from "react-native";
-import { useSafeAreaInsets } from "react-native-safe-area-context";
import {
ContentWrapper,
IOVisualCostants,
useIOTheme
} from "@pagopa/io-app-design-system";
+import * as React from "react";
+import { ScrollView, StatusBar, View, useColorScheme } from "react-native";
+import { useScreenEndMargin } from "../../../hooks/useScreenEndMargin";
type Props = {
title: string;
@@ -15,9 +15,10 @@ type Props = {
export const DesignSystemScreen = ({ children, noMargin = false }: Props) => {
const colorScheme = useColorScheme();
- const insets = useSafeAreaInsets();
const theme = useIOTheme();
+ const { screenEndMargin } = useScreenEndMargin();
+
return (
<>
{
{noMargin ? (
diff --git a/ts/features/design-system/core/DSScreenEndMargin.tsx b/ts/features/design-system/core/DSScreenEndMargin.tsx
new file mode 100644
index 00000000000..a136d1835f7
--- /dev/null
+++ b/ts/features/design-system/core/DSScreenEndMargin.tsx
@@ -0,0 +1,21 @@
+import { Body, IOVisualCostants } from "@pagopa/io-app-design-system";
+import * as React from "react";
+import { ScrollView } from "react-native";
+import { useScreenEndMargin } from "../../../hooks/useScreenEndMargin";
+
+export const DSScreenEndMargin = () => {
+ const { screenEndMargin } = useScreenEndMargin();
+
+ return (
+
+ {[...Array(50)].map((_el, i) => (
+ Repeated text
+ ))}
+
+ );
+};
diff --git a/ts/features/design-system/navigation/navigator.tsx b/ts/features/design-system/navigation/navigator.tsx
index dd1ff793e91..024ef194656 100644
--- a/ts/features/design-system/navigation/navigator.tsx
+++ b/ts/features/design-system/navigation/navigator.tsx
@@ -64,6 +64,7 @@ import { DSWizardScreen } from "../core/DSWizardScreen";
import DSListItemScreen from "../core/DSListItemScreen";
import { DSFooterActions } from "../core/DSFooterActions";
import { DSFooterActionsNotFixed } from "../core/DSFooterActionsNotFixed";
+import { DSScreenEndMargin } from "../core/DSScreenEndMargin";
import { DesignSystemParamsList } from "./params";
import DESIGN_SYSTEM_ROUTES from "./routes";
@@ -436,6 +437,14 @@ export const DesignSystemNavigator = () => {
options={{ headerShown: false }}
/>
+
+
{
const route = useRoute>();
+ const navigation = useNavigation();
+ const { screenEndMargin } = useScreenEndMargin();
+
// Navigation prop
const {
faqCategories,
@@ -223,45 +232,52 @@ const ZendeskSupportHelpCenter = () => {
addTicketCustomField(zendeskFciId, signatureRequestId ?? "");
}
+ useLayoutEffect(() => {
+ navigation.setOptions({
+ headerShown: true,
+ header: () => (
+
+ )
+ });
+ });
+
return (
- }
- customRightIcon={{
- iconName: "closeLarge",
- onPress: workUnitCancel,
- accessibilityLabel: I18n.t("global.accessibility.contextualHelp.close")
- }}
- headerTitle={I18n.t("support.helpCenter.header")}
+
-
-
-
+
+
- {showRequestSupportContacts && (
- <>
-
-
- >
- )}
-
-
-
-
+ {showRequestSupportContacts && (
+ <>
+
+
+ >
+ )}
+
+
);
};
diff --git a/ts/hooks/useScreenEndMargin.tsx b/ts/hooks/useScreenEndMargin.tsx
new file mode 100644
index 00000000000..263bbe1efbd
--- /dev/null
+++ b/ts/hooks/useScreenEndMargin.tsx
@@ -0,0 +1,51 @@
+import { IOSpacingScale, IOVisualCostants } from "@pagopa/io-app-design-system";
+import { useSafeAreaInsets } from "react-native-safe-area-context";
+
+type EndScreenSpacingValues = {
+ screenEndSafeArea: number;
+ screenEndMargin: number;
+};
+
+/**
+ * A custom React Hook that returns two spacing values that must be applied at
+ * the end of a ScrollView to prevent the content from being cut off.
+ * Depending on what you need, you can set one of these two values as `paddingBottom`
+ * using ScrollView's `contentContainerStyle` prop:
+ * - `screenEndSafeArea`
+ * The amount of safe area without additional margins. For devices that don't have safe area
+ * boundaries (e.g. iPhone with home button) it returns a fallback value that prevents content
+ * from sticking to the bottom.
+ * - `screenEndMargin`
+ * The total amount of space to add at the end of the ScrollView. It's a sum of the
+ * `screenSafeArea' value and the default `contentEndMargin' that should be applied
+ * at the end of each app screen.
+ */
+export const useScreenEndMargin = (): EndScreenSpacingValues => {
+ const insets = useSafeAreaInsets();
+
+ const needSafeAreaMargin = insets.bottom !== 0;
+
+ /* We use this fallback value to ensure that the spacing
+ is is consistent across all axes. */
+ const fallbackSafeAreaMargin = IOVisualCostants.appMarginDefault;
+
+ /* End content margin. If the devices don't have safe area
+ boundaries, we calculate the difference to get the same spacing
+ value as for devices that do have safe area boundaries. */
+ const contentEndMargin: IOSpacingScale = 32;
+ const computedContentEndMargin = needSafeAreaMargin
+ ? contentEndMargin
+ : contentEndMargin - fallbackSafeAreaMargin;
+
+ /* Check if the iPhone bottom handle is present.
+ If not add a default margin to prevent the button
+ from sticking to the bottom. */
+ const screenEndSafeArea = !needSafeAreaMargin
+ ? fallbackSafeAreaMargin
+ : insets.bottom;
+
+ return {
+ screenEndSafeArea,
+ screenEndMargin: screenEndSafeArea + computedContentEndMargin
+ };
+};