Skip to content

Commit

Permalink
[IOPLT-237] Refinement of ListItemRadio and RadioGroup (#136)
Browse files Browse the repository at this point in the history
## Short description
This PR introduces a refinement to `ListItemRadio` component to handle
loading state and support payment logo rendering in place of an icon in
exclusion of it.
ListItemRadio props `startImage` replaces the flat `icon` or
`paymentLogo` props to solve an issue in `RadioGroup` usage that was not
recognizing the correct prop spec in its usage.

## List of changes proposed in this pull request
- Refinement `ListItemRadio`
- Refinement `RadioGroup`

## How to test
Check the `Selection` page on example app.

---------

Co-authored-by: Damiano Plebani <[email protected]>
  • Loading branch information
CrisTofani and dmnplb authored Nov 22, 2023
1 parent 2f4962b commit 276bd17
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 18 deletions.
56 changes: 54 additions & 2 deletions example/src/pages/Selection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,18 @@ const renderListItemCheckbox = () => (

const mockRadioItems = (): ReadonlyArray<RadioItem<string>> => [
{
icon: "coggle",
startImage: { icon: "coggle" },
value: "Let's try with a basic title",
description:
"Ti contatteranno solo i servizi che hanno qualcosa di importante da dirti. Potrai sempre disattivare le comunicazioni che non ti interessano.",
id: "example-1"
id: "example-icon"
},
{
startImage: { paymentLogo: "myBank" },
value: "Let's try with a basic title",
description:
"Ti contatteranno solo i servizi che hanno qualcosa di importante da dirti. Potrai sempre disattivare le comunicazioni che non ti interessano.",
id: "example-paymentLogo"
},
{
value: "Let's try with a basic title",
Expand All @@ -169,6 +176,51 @@ const mockRadioItems = (): ReadonlyArray<RadioItem<string>> => [
"Ti contatteranno solo i servizi che hanno qualcosa di importante da dirti.",
id: "example-disabled",
disabled: true
},
{
value: "Let's try with a disabled item",
description:
"Ti contatteranno solo i servizi che hanno qualcosa di importante da dirti.",
id: "example-loading",
disabled: true,
loadingProps: {
state: true,
skeletonIcon: false
}
},
{
value: "Let's try with a disabled item",
description:
"Ti contatteranno solo i servizi che hanno qualcosa di importante da dirti.",
id: "example-loading-withIcon",
disabled: true,
loadingProps: {
state: true,
skeletonIcon: true
}
},
{
value: "Let's try with a disabled item",
description:
"Ti contatteranno solo i servizi che hanno qualcosa di importante da dirti.",
id: "example-loading-withDescription",
disabled: true,
loadingProps: {
state: true,
skeletonDescription: true
}
},
{
value: "Let's try with a disabled item",
description:
"Ti contatteranno solo i servizi che hanno qualcosa di importante da dirti.",
id: "example-loading-withIcon-withDescription",
disabled: true,
loadingProps: {
state: true,
skeletonDescription: true,
skeletonIcon: true
}
}
];

Expand Down
111 changes: 99 additions & 12 deletions src/components/listitems/ListItemRadio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Animated, {
useSharedValue,
withSpring
} from "react-native-reanimated";
import Placeholder from "rn-placeholder";
import {
IOColors,
IOScaleValues,
Expand All @@ -26,13 +27,31 @@ import { IOIcons, Icon } from "../icons";
import { HSpacer, VSpacer } from "../spacer";
import { H6, LabelSmall } from "../typography";
import { AnimatedRadio } from "../radio/AnimatedRadio";
import { IOLogoPaymentType, LogoPayment } from "../logos";

type ListItemRadioGraphicProps =
| { icon?: never; paymentLogo: IOLogoPaymentType }
| { icon: IOIcons; paymentLogo?: never };

type ListItemRadioLoadingProps =
| {
state: true;
skeletonDescription?: boolean;
skeletonIcon?: boolean;
}
| {
state?: false;
skeletonDescription?: never;
skeletonIcon?: never;
};

type Props = WithTestID<{
value: string;
description?: string;
icon?: IOIcons;
selected: boolean;
onValueChange?: (newValue: boolean) => void;
startImage?: ListItemRadioGraphicProps;
loadingProps?: ListItemRadioLoadingProps;
}>;

const DISABLED_OPACITY = 0.5;
Expand All @@ -46,7 +65,7 @@ type OwnProps = Props &
>;

/**
* with the automatic state management that uses a {@link AnimatedCheckBox}
* `ListItemRadio` component with the automatic state management that uses a {@link AnimatedCheckBox}
* The toggleValue change when a `onPress` event is received and dispatch the `onValueChange`.
*
* @param props
Expand All @@ -55,16 +74,16 @@ type OwnProps = Props &
export const ListItemRadio = ({
value,
description,
icon,
startImage,
selected,
disabled,
onValueChange,
loadingProps,
testID
}: OwnProps) => {
const [toggleValue, setToggleValue] = useState(selected ?? false);
// Animations
const isPressed: Animated.SharedValue<number> = useSharedValue(0);

// Scaling transformation applied when the button is pressed
const animationScaleValue = IOScaleValues?.basicButton?.pressedState;

Expand Down Expand Up @@ -123,7 +142,66 @@ export const ListItemRadio = ({
}
};

return (
const disabledStyle = { opacity: disabled ? DISABLED_OPACITY : 1 };

const SkeletonDescriptionLines = () => (
<>
<VSpacer size={8} />
<Placeholder.Box animate="fade" radius={8} width={"100%"} height={8} />
<VSpacer size={8} />
<Placeholder.Box animate="fade" radius={8} width={"100%"} height={8} />
<VSpacer size={8} />
<Placeholder.Box animate="fade" radius={8} width={"100%"} height={8} />
</>
);

const SkeletonIcon = () => (
<View
style={{
marginRight: IOSelectionListItemVisualParams.iconMargin
}}
>
<Placeholder.Box
animate="fade"
radius={4}
width={IOSelectionListItemVisualParams.iconSize}
height={IOSelectionListItemVisualParams.iconSize}
/>
</View>
);

const SkeletonComponent = () => (
<View style={IOSelectionListItemStyles.listItem}>
<View style={IOSelectionListItemStyles.listItemInner}>
<View
style={[
IOStyles.flex,
IOStyles.rowSpaceBetween,
IOStyles.alignCenter
]}
>
<View style={[IOStyles.row, IOStyles.alignCenter]}>
{loadingProps?.skeletonIcon && <SkeletonIcon />}
<Placeholder.Box
animate="fade"
radius={8}
width={179}
height={16}
/>
</View>
<HSpacer size={8} />
<View pointerEvents="none" style={disabledStyle}>
<AnimatedRadio checked={toggleValue} />
</View>
</View>
</View>
{loadingProps?.skeletonDescription && <SkeletonDescriptionLines />}
</View>
);

return loadingProps?.state ? (
<SkeletonComponent />
) : (
<Pressable
onPress={toggleRadioItem}
onPressIn={handlePressIn}
Expand All @@ -136,7 +214,7 @@ export const ListItemRadio = ({
style={[
IOSelectionListItemStyles.listItem,
animatedBackgroundStyle,
{ opacity: disabled ? DISABLED_OPACITY : 1 }
disabledStyle
]}
// This is required to avoid opacity
// inheritance on Android
Expand All @@ -145,17 +223,26 @@ export const ListItemRadio = ({
<Animated.View style={animatedScaleStyle}>
<View style={IOSelectionListItemStyles.listItemInner}>
<View style={[IOStyles.row, { flexShrink: 1 }]}>
{icon && (
{startImage && (
<View
style={{
marginRight: IOSelectionListItemVisualParams.iconMargin
}}
>
<Icon
name={icon}
color="grey-300"
size={IOSelectionListItemVisualParams.iconSize}
/>
{/* icon or paymentLogo props are mutually exclusive */}
{startImage.icon && (
<Icon
name={startImage.icon}
color="grey-300"
size={IOSelectionListItemVisualParams.iconSize}
/>
)}
{startImage.paymentLogo && (
<LogoPayment
name={startImage.paymentLogo}
size={IOSelectionListItemVisualParams.iconSize}
/>
)}
</View>
)}

Expand Down
9 changes: 5 additions & 4 deletions src/components/radio/RadioGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React from "react";
import React, { ComponentProps } from "react";
import { View } from "react-native";
import { Divider } from "../divider";
import { IOIcons } from "../icons";
import { ListItemRadio } from "../listitems/ListItemRadio";

export type RadioItem<T> = {
id: T;
value: string;
description?: string;
icon?: IOIcons;
disabled?: boolean;
startImage?: ComponentProps<typeof ListItemRadio>["startImage"];
loadingProps?: ComponentProps<typeof ListItemRadio>["loadingProps"];
};

type Props<T> = {
Expand All @@ -31,8 +31,9 @@ export const RadioGroup = <T,>({ items, selectedItem, onPress }: Props<T>) => (
testID={`RadioItemTestID_${item.id}`}
value={item.value}
description={item.description}
icon={item.icon}
startImage={item.startImage}
disabled={item.disabled}
loadingProps={item.loadingProps}
onValueChange={() => onPress(item.id)}
selected={selectedItem === item.id}
/>
Expand Down

0 comments on commit 276bd17

Please sign in to comment.