Skip to content

Commit

Permalink
[IOPAE-1195] Add AvatarSearch component (#274)
Browse files Browse the repository at this point in the history
## Short description
This PR introduces `AvatarSearch` component, which will be used in the
search results of institutions.

## List of changes proposed in this pull request
- Added `AvatarSearch` component

---------

Co-authored-by: Damiano Plebani <[email protected]>
  • Loading branch information
adelloste and dmnplb authored May 29, 2024
1 parent 13d2d10 commit 4c79d77
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 53 deletions.
102 changes: 66 additions & 36 deletions example/src/pages/Logos.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {
Avatar,
AvatarSearch,
H2,
HSpacer,
HStack,
IOColors,
IOLogoPaymentCardType,
IOLogoPaymentExtType,
Expand Down Expand Up @@ -142,54 +144,82 @@ const organizationsURIs = [

const renderAvatar = () => (
<>
<ComponentViewerBox name={`Avatar, small size, square shape`}>
<ComponentViewerBox name={`Avatar, small size`}>
<ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
style={styles.horizontalScroll}
>
{organizationsURIs.map(({ imageSource }, i) => (
<React.Fragment key={i}>
<Avatar
size="small"
logoUri={
imageSource
? Array.isArray(imageSource)
? imageSource.map(s => ({ uri: s }))
: {
uri: imageSource
}
: undefined
}
/>
{i < organizationsURIs.length - 1 && <HSpacer size={8} />}
</React.Fragment>
))}
<HStack space={8}>
{organizationsURIs.map(({ imageSource }, i) => (
<React.Fragment key={i}>
<Avatar
size="small"
logoUri={
imageSource
? Array.isArray(imageSource)
? imageSource.map(s => ({ uri: s }))
: {
uri: imageSource
}
: undefined
}
/>
{i === organizationsURIs.length - 1 && <HSpacer size={32} />}
</React.Fragment>
))}
</HStack>
</ScrollView>
</ComponentViewerBox>
<ComponentViewerBox name={`Avatar, medium size, square shape`}>
<ComponentViewerBox name={`Avatar, medium size`}>
<ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
style={styles.horizontalScroll}
>
{organizationsURIs.map(({ imageSource }, i) => (
<React.Fragment key={i}>
<Avatar
size="medium"
logoUri={
imageSource
? Array.isArray(imageSource)
? imageSource.map(s => ({ uri: s }))
: {
uri: imageSource
}
: undefined
}
/>
{i < organizationsURIs.length - 1 && <HSpacer size={8} />}
</React.Fragment>
))}
<HStack space={8}>
{organizationsURIs.map(({ imageSource }, i) => (
<React.Fragment key={i}>
<Avatar
size="medium"
logoUri={
imageSource
? Array.isArray(imageSource)
? imageSource.map(s => ({ uri: s }))
: {
uri: imageSource
}
: undefined
}
/>
{i === organizationsURIs.length - 1 && <HSpacer size={32} />}
</React.Fragment>
))}
</HStack>
</ScrollView>
</ComponentViewerBox>
<ComponentViewerBox name={`AvatarSearch`}>
<ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
style={styles.horizontalScroll}
>
<HStack space={8}>
{organizationsURIs.map(({ imageSource }, i) => (
<React.Fragment key={i}>
<AvatarSearch
source={
imageSource
? Array.isArray(imageSource)
? imageSource.map(s => ({ uri: s }))
: [{ uri: imageSource }]
: []
}
/>
{i === organizationsURIs.length - 1 && <HSpacer size={32} />}
</React.Fragment>
))}
</HStack>
</ScrollView>
</ComponentViewerBox>
</>
Expand Down
57 changes: 52 additions & 5 deletions src/components/avatar/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as React from "react";
import React, { ComponentProps } from "react";
import {
Image,
ImageRequireSource,
Expand All @@ -15,12 +15,9 @@ import {
useIOTheme
} from "../../core";
import { addCacheTimestampToUri } from "../../utils/image";
import avatarSearchPlaceholder from "./placeholder/avatar-placeholder.png";

type Avatar = {
/**
* @deprecated Only `square` shape variant accepted
*/
shape?: "circle" | "square";
size: "small" | "medium";
logoUri?: ImageRequireSource | ImageURISource | ReadonlyArray<ImageURISource>;
};
Expand Down Expand Up @@ -142,3 +139,53 @@ export const Avatar = ({ logoUri, size }: Avatar) => {
</View>
);
};

export type AvatarSearchProps = Pick<
ComponentProps<typeof Image>,
"source" | "defaultSource"
>;

/**
* AvatarSearch component is used to display the logo of an institution in the search results.
* A placeholder is displayed if the logo is not available.
* Note: On Android, the default source prop is ignored on debug builds.
*
* @param AvatarSearchProps
* @returns
*/
export const AvatarSearch = React.memo(
({ defaultSource, source }: AvatarSearchProps) => {
// Visual attributes
const avatarSize = dimensionsMap.small.size;
const borderRadius = dimensionsMap.small.radius;
const internalSpace = dimensionsMap.small.internalSpace;
const innerRadius = borderRadius - internalSpace;

return (
<View
accessibilityIgnoresInvertColors
style={[
styles.avatarWrapper,
{
borderRadius,
height: avatarSize,
width: avatarSize,
backgroundColor: IOColors.white,
padding: internalSpace
}
]}
>
<View
style={[styles.avatarInnerWrapper, { borderRadius: innerRadius }]}
>
<Image
accessibilityIgnoresInvertColors
source={source}
style={styles.avatarImage}
defaultSource={defaultSource ?? avatarSearchPlaceholder}
/>
</View>
</View>
);
}
);
118 changes: 118 additions & 0 deletions src/components/avatar/__test__/__snapshots__/avatar.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,65 @@ exports[`Test Avatar Components - Experimental Enabled Avatar Snapshot 1`] = `
</View>
`;

exports[`Test Avatar Components - Experimental Enabled AvatarSearch Snapshot 1`] = `
<View
accessibilityIgnoresInvertColors={true}
style={
[
{
"borderColor": "rgba(14,15,19,0.1)",
"borderCurve": "continuous",
"borderWidth": 1,
"overflow": "hidden",
},
{
"backgroundColor": "#FFFFFF",
"borderRadius": 8,
"height": 44,
"padding": 6,
"width": 44,
},
]
}
>
<View
style={
[
{
"backgroundColor": "#FFFFFF",
"borderCurve": "continuous",
"overflow": "hidden",
},
{
"borderRadius": 2,
},
]
}
>
<Image
accessibilityIgnoresInvertColors={true}
defaultSource={
{
"testUri": "../../../src/components/avatar/placeholder/avatar-placeholder.png",
}
}
source={
{
"uri": "",
}
}
style={
{
"height": "100%",
"resizeMode": "contain",
"width": "100%",
}
}
/>
</View>
</View>
`;

exports[`Test Avatar Components Avatar Snapshot 1`] = `
<View
accessibilityIgnoresInvertColors={true}
Expand Down Expand Up @@ -109,3 +168,62 @@ exports[`Test Avatar Components Avatar Snapshot 1`] = `
</View>
</View>
`;

exports[`Test Avatar Components AvatarSearch Snapshot 1`] = `
<View
accessibilityIgnoresInvertColors={true}
style={
[
{
"borderColor": "rgba(14,15,19,0.1)",
"borderCurve": "continuous",
"borderWidth": 1,
"overflow": "hidden",
},
{
"backgroundColor": "#FFFFFF",
"borderRadius": 8,
"height": 44,
"padding": 6,
"width": 44,
},
]
}
>
<View
style={
[
{
"backgroundColor": "#FFFFFF",
"borderCurve": "continuous",
"overflow": "hidden",
},
{
"borderRadius": 2,
},
]
}
>
<Image
accessibilityIgnoresInvertColors={true}
defaultSource={
{
"testUri": "../../../src/components/avatar/placeholder/avatar-placeholder.png",
}
}
source={
{
"uri": "",
}
}
style={
{
"height": "100%",
"resizeMode": "contain",
"width": "100%",
}
}
/>
</View>
</View>
`;
20 changes: 17 additions & 3 deletions src/components/avatar/__test__/avatar.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import React from "react";
import * as TestRenderer from "react-test-renderer";
import { TestRendererWithExperimentalEnabledContextProvider } from "../../../utils/testing";
import { Avatar } from "../Avatar";
import { Avatar, AvatarSearch } from "../Avatar";

describe("Test Avatar Components", () => {
it("Avatar Snapshot", () => {
const avatar = TestRenderer.create(
<Avatar shape={"circle"} size={"small"} logoUri={{ uri: "" }}></Avatar>
<Avatar size={"small"} logoUri={{ uri: "" }} />
).toJSON();
expect(avatar).toMatchSnapshot();
});

it("AvatarSearch Snapshot", () => {
const avatar = TestRenderer.create(
<AvatarSearch source={{ uri: "" }} />
).toJSON();
expect(avatar).toMatchSnapshot();
});
Expand All @@ -15,7 +22,14 @@ describe("Test Avatar Components", () => {
describe("Test Avatar Components - Experimental Enabled", () => {
it("Avatar Snapshot", () => {
const avatar = TestRendererWithExperimentalEnabledContextProvider(
<Avatar shape={"circle"} size={"small"} logoUri={{ uri: "" }}></Avatar>
<Avatar size={"small"} logoUri={{ uri: "" }} />
).toJSON();
expect(avatar).toMatchSnapshot();
});

it("AvatarSearch Snapshot", () => {
const avatar = TestRendererWithExperimentalEnabledContextProvider(
<AvatarSearch source={{ uri: "" }} />
).toJSON();
expect(avatar).toMatchSnapshot();
});
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/components/listitems/ListItemTransaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ const MUNICIPALITY_LOGO_SIZE = 44;

const LeftComponent = ({ logoIcon }: LeftComponentProps) => {
if (isImageUri(logoIcon)) {
return <Avatar logoUri={logoIcon} size="small" shape="circle" />;
return <Avatar logoUri={logoIcon} size="small" />;
}
if (React.isValidElement(logoIcon)) {
return <>{logoIcon}</>;
Expand Down Expand Up @@ -250,7 +250,7 @@ const SkeletonComponent = () => (
animate="fade"
height={IOVisualCostants.avatarSizeSmall}
width={IOVisualCostants.avatarSizeSmall}
radius={100}
radius={IOVisualCostants.avatarRadiusSizeSmall}
/>
</View>
<View style={IOStyles.flex}>
Expand Down
Loading

0 comments on commit 4c79d77

Please sign in to comment.