Skip to content

Commit

Permalink
chore(Cross): [IOAPPX-307] Add useScreenEndMargin hook (#5808)
Browse files Browse the repository at this point in the history
## Short description
This PR adds the new `useScreenEndMargin`, a specific hook to use when
you have to develop a new screen and you want to get a safe numeric
value to set as `paddingBottom` for the `ScrollView`. This hook
automatically takes into account each device configuration (devices with
safe area boundaries and devices without)

This should also help to prevent the inclusion of `SafeAreaView` (or
similar) which results in a white block at the corresponding bottom safe
area (see image below):

<img
src="https://github.com/pagopa/io-app/assets/1255491/d771c74b-cce2-4d0c-93a7-b490cbcab6a1"
width="240" />

> [!note]
> To learn more about the technical details, take a look at the hook
file and its comments

> [!important]
> If you already use `FooterActions` or `IOScrollView` you don't need
this hook because the end margin is already taken into account

## List of changes proposed in this pull request
- Add `useScreenEndMargin` hook
- Add relative example screen in the DS (**Screen End Margin**)

## How to test
- Launch the app in the local environment
- Go to the **Design System** → **Screen End Margin** (under **Debug**)
  • Loading branch information
dmnplb authored Jun 6, 2024
1 parent a7fb875 commit af7b8a6
Show file tree
Hide file tree
Showing 10 changed files with 318 additions and 221 deletions.
154 changes: 75 additions & 79 deletions ts/components/ui/FooterActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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] =
Expand All @@ -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;
Expand Down Expand Up @@ -247,48 +215,76 @@ export const FooterActions = ({
<Text style={styles.debugText}>{`Height: ${actionBlockHeight}`}</Text>
)}

{primaryAction && <ButtonSolid fullWidth {...primaryAction} />}
{renderActions(actions, extraBottomMargin)}
</View>
</Animated.View>
);
};

const useBottomMargins = (
actions: FooterActions | undefined,
excludeSafeAreaMargins: boolean
) => {
const insets = useSafeAreaInsets();
const needSafeAreaMargin = insets.bottom !== 0;

{type === "TwoButtons" && (
<View
style={{
alignSelf: "center",
marginBottom: extraBottomMargin
}}
>
<VSpacer size={spaceBetweenActionAndLink} />
{secondaryAction && (
<ButtonLink
color="primary"
{...(secondaryAction as ComponentProps<typeof ButtonLink>)}
/>
)}
</View>
)}
/* 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" && (
<Fragment>
{secondaryAction && (
<Fragment>
<VSpacer size={spaceBetweenActions} />
<ButtonOutline fullWidth color="primary" {...secondaryAction} />
</Fragment>
)}
/* 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 && (
<View
style={{
alignSelf: "center",
marginBottom: extraBottomMargin
}}
>
<VSpacer size={spaceBetweenActionAndLink} />
<ButtonLink color="primary" {...tertiaryAction} />
</View>
)}
</Fragment>
)}
</View>
</Animated.View>
return { bottomMargin, extraBottomMargin };
};

const renderActions = (
actions: FooterActions | undefined,
extraBottomMargin: number
) => {
if (!actions) {
return null;
}
const {
type,
primary: primaryAction,
secondary: secondaryAction,
tertiary: tertiaryAction
} = actions;
return (
<Fragment>
{primaryAction && <ButtonSolid fullWidth {...primaryAction} />}
{type === "TwoButtons" && secondaryAction && (
<View style={{ alignSelf: "center", marginBottom: extraBottomMargin }}>
<VSpacer size={spaceBetweenActionAndLink} />
<ButtonLink color="primary" {...secondaryAction} />
</View>
)}
{type === "ThreeButtons" && (
<>
{secondaryAction && (
<>
<VSpacer size={spaceBetweenActions} />
<ButtonOutline fullWidth color="primary" {...secondaryAction} />
</>
)}
{tertiaryAction && (
<View
style={{ alignSelf: "center", marginBottom: extraBottomMargin }}
>
<VSpacer size={spaceBetweenActionAndLink} />
<ButtonLink color="primary" {...tertiaryAction} />
</View>
)}
</>
)}
</Fragment>
);
};
Loading

0 comments on commit af7b8a6

Please sign in to comment.