Skip to content

Commit

Permalink
[IOAPPX-353] Add the new FooterActions (from io-app) and `FooterA…
Browse files Browse the repository at this point in the history
…ctionsInline` (#316)

## Short description
This PR adds the new `FooterActions` (migrated from `io-app`) and the
new `FooterActionsInline`, which replaces the legacy
`FooterWithButtons`. So if you want to add actions to the footer you
have several options:
- **If you need a single button**, just use `FooterActions` with the
`SingleButton` type
- **If you need two buttons**, depending on your needs:
  - use `FooterActions` as recommended in the official guidelines
- for special reasons (e.g. you need to save space), use
`FooterActionsInline` to render both buttons on the same line
- **If you need three buttons**, just use `FooterActions` with the
`ThreeButtons` type

Both `FooterActions` and `FooterActionsInline` come with two hooks,
`useFooterActionsMeasurements` and `useFooterActionsInlineMeasurements`
respectively, to get the `safeBottomAreaHeight` value to apply to the
`ScrollView`'s `paddingBottom`

## List of changes proposed in this pull request
- Add the new `FooterActions` and `FooterActionsInline` and relative
hooks
- Add the new demo screens to the example app
- Add the new `IOSpacing` object with the common spacing constants
- Update the deprecation note to `FooterWithButtons`
- Add the deprecation note to `BlockButtons`

### Preview


https://github.com/user-attachments/assets/027914cf-3427-4bed-9a0e-9f208fd66c49



## How to test
1. Launch the example app
2. Go to the `FooterActions…` screens

---------

Co-authored-by: Cristiano Tofani <[email protected]>
  • Loading branch information
dmnplb and CrisTofani authored Oct 9, 2024
1 parent 492b8a6 commit ef084b2
Show file tree
Hide file tree
Showing 26 changed files with 1,061 additions and 136 deletions.
65 changes: 53 additions & 12 deletions example/src/navigation/navigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,19 @@ import { DSAlert } from "../pages/Alert";
import { Badges } from "../pages/Badges";
import { Buttons } from "../pages/Buttons";
import { Colors } from "../pages/Colors";
import { FooterWithButton } from "../pages/FooterWithButton";
import { FooterWithButtonEmptyState } from "../pages/FooterWithButtonEmptyState";
import { FooterActionsEmptyStateScreen } from "../pages/FooterActionsEmptyStateScreen";
import { FooterActionsInlineNotFixed } from "../pages/FooterActionsInlineNotFixed";
import { FooterActionsInlineScreen } from "../pages/FooterActionsInlineScreen";
import { FooterActionsNotFixed } from "../pages/FooterActionsNotFixed";
import { FooterActionsScreen } from "../pages/FooterActionsScreen";
import { FooterActionsStickyScreen } from "../pages/FooterActionsStickyScreen";
import { ForceScrollDownViewPage } from "../pages/ForceScrollDownViewPage";
import { GradientScroll } from "../pages/GradientScroll";
import { HeaderFirstLevelScreen } from "../pages/HeaderFirstLevel";
import { HeaderSecondLevelScreen } from "../pages/HeaderSecondLevel";
import { HeaderSecondLevelCustomBackground } from "../pages/HeaderSecondLevelCustomBackground";
import { HeaderSecondLevelScreenDiscreteTransition } from "../pages/HeaderSecondLevelDiscreteTransition";
import { HeaderSecondLevelScreenDiscreteTransitionCustomBg } from "../pages/HeaderSecondLevelScreenDiscreteTransitionCustomBg";
import { HeaderSecondLevelWithStepper } from "../pages/HeaderSecondLevelWithStepper";
import { Icons } from "../pages/Icons";
import { ImageScreen } from "../pages/Image";
Expand All @@ -39,6 +46,7 @@ import { NumberPadScreen } from "../pages/NumberPad";
import { OTPInputScreen } from "../pages/OTPInput";
import { Pictograms } from "../pages/Pictograms";
import { Sandbox } from "../pages/Sandbox";
import { SearchCustom } from "../pages/SearchCustom";
import { SearchNative } from "../pages/SearchNative";
import { Selection } from "../pages/Selection";
import { StaticHeaderSecondLevelScreen } from "../pages/StaticHeaderSecondLevel";
Expand All @@ -47,10 +55,6 @@ import { TabNavigationScreen } from "../pages/TabNavigation";
import { TextInputs } from "../pages/TextInputs";
import { Toasts } from "../pages/Toasts";
import { Typography } from "../pages/Typography";
import { SearchCustom } from "../pages/SearchCustom";
import { HeaderSecondLevelCustomBackground } from "../pages/HeaderSecondLevelCustomBackground";
import { HeaderSecondLevelScreenDiscreteTransition } from "../pages/HeaderSecondLevelDiscreteTransition";
import { HeaderSecondLevelScreenDiscreteTransitionCustomBg } from "../pages/HeaderSecondLevelScreenDiscreteTransitionCustomBg";
import { AppParamsList } from "./params";
import APP_ROUTES from "./routes";

Expand Down Expand Up @@ -275,20 +279,57 @@ const AppNavigator = () => {
/>

<Stack.Screen
name={APP_ROUTES.SCREENS.FOOTER_WITH_BUTTON.route}
component={FooterWithButton}
name={APP_ROUTES.SCREENS.FOOTER_ACTIONS.route}
component={FooterActionsScreen}
options={{
headerTitle: APP_ROUTES.SCREENS.FOOTER_WITH_BUTTON.title,
headerTitle: APP_ROUTES.SCREENS.FOOTER_ACTIONS.title,
headerBackTitleVisible: false
}}
/>

<Stack.Screen
name={APP_ROUTES.SCREENS.FOOTER_ACTIONS_STICKY.route}
component={FooterActionsStickyScreen}
options={{
headerTitle: APP_ROUTES.SCREENS.FOOTER_ACTIONS_STICKY.title,
headerBackTitleVisible: false
}}
/>

<Stack.Screen
name={APP_ROUTES.SCREENS.FOOTER_ACTIONS_NOT_FIXED.route}
component={FooterActionsNotFixed}
options={{
headerTitle: APP_ROUTES.SCREENS.FOOTER_ACTIONS_NOT_FIXED.title,
headerBackTitleVisible: false
}}
/>

<Stack.Screen
name={APP_ROUTES.SCREENS.FOOTER_ACTIONS_INLINE.route}
component={FooterActionsInlineScreen}
options={{
headerTitle: APP_ROUTES.SCREENS.FOOTER_ACTIONS_INLINE.title,
headerBackTitleVisible: false
}}
/>

<Stack.Screen
name={APP_ROUTES.SCREENS.FOOTER_ACTIONS_INLINE_NOT_FIXED.route}
component={FooterActionsInlineNotFixed}
options={{
headerTitle:
APP_ROUTES.SCREENS.FOOTER_ACTIONS_INLINE_NOT_FIXED.title,
headerBackTitleVisible: false
}}
/>

<Stack.Screen
name={APP_ROUTES.SCREENS.FOOTER_WITH_BUTTON_EMPTY.route}
component={FooterWithButtonEmptyState}
name={APP_ROUTES.SCREENS.FOOTER_ACTIONS_EMPTY_STATE.route}
component={FooterActionsEmptyStateScreen}
options={{
headerShown: false,
headerTitle: APP_ROUTES.SCREENS.FOOTER_WITH_BUTTON.title,
headerTitle: APP_ROUTES.SCREENS.FOOTER_ACTIONS_EMPTY_STATE.title,
headerBackTitleVisible: false
}}
/>
Expand Down
9 changes: 7 additions & 2 deletions example/src/navigation/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,14 @@ export type AppParamsList = {
[DESIGN_SYSTEM_ROUTES.COMPONENTS.HEADER_SECOND_LEVEL_STATIC.route]: undefined;
[DESIGN_SYSTEM_ROUTES.COMPONENTS.HEADER_SECOND_LEVEL_STEPPER
.route]: undefined;
[DESIGN_SYSTEM_ROUTES.SCREENS.FOOTER_WITH_BUTTON.route]: undefined;
[DESIGN_SYSTEM_ROUTES.SCREENS.FOOTER_ACTIONS_INLINE.route]: undefined;
[DESIGN_SYSTEM_ROUTES.SCREENS.FOOTER_ACTIONS_INLINE_NOT_FIXED
.route]: undefined;
[DESIGN_SYSTEM_ROUTES.SCREENS.FOOTER_ACTIONS.route]: undefined;
[DESIGN_SYSTEM_ROUTES.SCREENS.FOOTER_ACTIONS_STICKY.route]: undefined;
[DESIGN_SYSTEM_ROUTES.SCREENS.FOOTER_ACTIONS_NOT_FIXED.route]: undefined;
[DESIGN_SYSTEM_ROUTES.SCREENS.FOOTER_ACTIONS_EMPTY_STATE.route]: undefined;
[DESIGN_SYSTEM_ROUTES.SCREENS.GRADIENT_SCROLLVIEW.route]: undefined;
[DESIGN_SYSTEM_ROUTES.SCREENS.FOOTER_WITH_BUTTON_EMPTY.route]: undefined;
[DESIGN_SYSTEM_ROUTES.COMPONENTS.TOASTS.route]: undefined;
[DESIGN_SYSTEM_ROUTES.SCREENS.FULL_SCREEN_MODAL.route]: undefined;
[DESIGN_SYSTEM_ROUTES.SCREENS.FULL_SCREEN_MODAL_2.route]: undefined;
Expand Down
28 changes: 22 additions & 6 deletions example/src/navigation/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,29 @@ const APP_ROUTES = {
title: "Full screen modal (second example)"
},
SEARCH: { route: "DESIGN_SYSTEM_SEARCHBAR", title: "Search" },
FOOTER_WITH_BUTTON_EMPTY: {
route: "DESIGN_SYSTEM_FOOTER_WITH_BUTTON_EMPTY",
title: "Footer with button (Empty state)"
FOOTER_ACTIONS: {
route: "DESIGN_SYSTEM_FOOTER_ACTIONS",
title: "Footer actions"
},
FOOTER_WITH_BUTTON: {
route: "DESIGN_SYSTEM_FOOTER_WITH_BUTTON",
title: "Footer with button"
FOOTER_ACTIONS_STICKY: {
route: "DESIGN_SYSTEM_FOOTER_ACTIONS_STICKY",
title: "Footer actions (sticky)"
},
FOOTER_ACTIONS_NOT_FIXED: {
route: "DESIGN_SYSTEM_FOOTER_ACTIONS_NOT_FIXED",
title: "Footer actions (not fixed)"
},
FOOTER_ACTIONS_EMPTY_STATE: {
route: "DESIGN_SYSTEM_FOOTER_ACTIONS_EMPTY_STATE",
title: "Footer actions (Empty state)"
},
FOOTER_ACTIONS_INLINE: {
route: "DESIGN_SYSTEM_FOOTER_ACTIONS_INLINE",
title: "Footer actions (inline)"
},
FOOTER_ACTIONS_INLINE_NOT_FIXED: {
route: "DESIGN_SYSTEM_FOOTER_ACTIONS_INLINE_NOT_FIXED",
title: "Footer actions (inline, not fixed)"
},
GRADIENT_SCROLLVIEW: {
route: "DESIGN_SYSTEM_GRADIENT_SCROLLVIEW",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import {
Body,
ContentWrapper,
FooterWithButtons,
FooterActions,
H2,
IOColors,
VSpacer
VSpacer,
useFooterActionsMeasurements
} from "@pagopa/io-app-design-system";
import * as React from "react";
import { useState } from "react";
import { Alert, Platform, ScrollView, View } from "react-native";

/**
* This Screen is used to test components in isolation while developing.
* @returns a screen with a flexed view where you can test components
*/
export const FooterWithButtonEmptyState = () => {
const [footerHeight, setFooterHeight] = useState(0);
export const FooterActionsEmptyStateScreen = () => {
const { footerActionsMeasurements, handleFooterActionsMeasurements } =
useFooterActionsMeasurements();

return (
<View
Expand All @@ -27,7 +28,12 @@ export const FooterWithButtonEmptyState = () => {
{/* This extra View is mandatory when you have a fixed
bottom component to get a consistent behavior
across platforms */}
<View style={{ flexGrow: 1, paddingBottom: footerHeight }}>
<View
style={{
flexGrow: 1,
paddingBottom: footerActionsMeasurements.safeBottomAreaHeight
}}
>
<ScrollView
centerContent
contentContainerStyle={[
Expand All @@ -49,28 +55,15 @@ export const FooterWithButtonEmptyState = () => {
</ContentWrapper>
</ScrollView>
</View>
<FooterWithButtons
onLayoutChange={setFooterHeight}
sticky={true}
primary={{
type: "Solid",
buttonProps: {
color: "primary",
accessibilityLabel: "primary button",
onPress: () => Alert.alert("Button pressed"),
label: "Primary button"
<FooterActions
onMeasure={handleFooterActionsMeasurements}
actions={{
type: "SingleButton",
primary: {
label: "Pay button",
onPress: () => Alert.alert("Button pressed")
}
}}
// secondary={{
// type: "Outline",
// buttonProps: {
// color: "primary",
// accessibilityLabel: "secondary button",
// onPress: () => Alert.alert("Button pressed"),
// label: "Secondary button"
// }
// }}
type="SingleButton"
/>
</View>
);
Expand Down
55 changes: 55 additions & 0 deletions example/src/pages/FooterActionsInlineNotFixed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
FooterActionsInline,
IOColors,
VSpacer,
useIOTheme
} from "@pagopa/io-app-design-system";
import React from "react";
import { Alert, ScrollView, StyleSheet, Text, View } from "react-native";

const onButtonPress = () => {
Alert.alert("Alert", "Action triggered");
};

export const FooterActionsInlineNotFixed = () => {
const theme = useIOTheme();

return (
<ScrollView>
{[...Array(9)].map((_el, i) => (
<React.Fragment key={`view-${i}`}>
<View
style={[
styles.block,
{ backgroundColor: IOColors[theme["appBackground-secondary"]] }
]}
>
<Text style={{ color: IOColors[theme["textBody-tertiary"]] }}>
{`Block ${i}`}
</Text>
</View>
<VSpacer size={4} />
</React.Fragment>
))}
<FooterActionsInline
fixed={false}
startAction={{
label: "Secondary",
onPress: onButtonPress
}}
endAction={{
label: "Primary",
onPress: onButtonPress
}}
/>
</ScrollView>
);
};

const styles = StyleSheet.create({
block: {
alignItems: "center",
justifyContent: "center",
aspectRatio: 16 / 10
}
});
60 changes: 60 additions & 0 deletions example/src/pages/FooterActionsInlineScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
Body,
ButtonOutline,
ContentWrapper,
FooterActionsInline,
H1,
useFooterActionsInlineMeasurements
} from "@pagopa/io-app-design-system";
import * as React from "react";
import { Alert, ScrollView, View } from "react-native";

/**
* This Screen is used to test components in isolation while developing.
* @returns a screen with a flexed view where you can test components
*/
export const FooterActionsInlineScreen = () => {
const {
footerActionsInlineMeasurements,
handleFooterActionsInlineMeasurements
} = useFooterActionsInlineMeasurements();

return (
<View>
<ScrollView
contentContainerStyle={{
paddingBottom: footerActionsInlineMeasurements.safeBottomAreaHeight
}}
>
<ContentWrapper>
<H1>Footer Actions (inline)</H1>
{[...Array(50)].map((_el, i) => (
<Body key={`body-${i}`}>{`Repeated text ${i}`}</Body>
))}
<ButtonOutline
color="primary"
accessibilityLabel="Test ButtonOutline"
onPress={() => Alert.alert("Button pressed")}
label="Test Button"
/>
{[...Array(10)].map((_el, i) => (
<Body key={`body-${i}`}>{`Repeated text ${i}`}</Body>
))}
</ContentWrapper>
</ScrollView>
<FooterActionsInline
onMeasure={handleFooterActionsInlineMeasurements}
startAction={{
color: "primary",
label: "Outline button",
onPress: () => Alert.alert("Button pressed")
}}
endAction={{
color: "primary",
onPress: () => Alert.alert("Button pressed"),
label: "Solid button"
}}
/>
</View>
);
};
Loading

0 comments on commit ef084b2

Please sign in to comment.