Skip to content

Commit

Permalink
Create BadgeTile
Browse files Browse the repository at this point in the history
  • Loading branch information
graemehouston committed Aug 31, 2024
1 parent 241d4d9 commit db36922
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 142 deletions.
47 changes: 37 additions & 10 deletions lib/components/atoms/BaseTile/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React, { createContext, FC, ReactNode, useContext } from 'react';
import { StyleSheet, View, Image, ImageProps } from 'react-native';
import Color from 'color';
import { Text } from '../../atoms';
import { Icon, Text } from '../../atoms';
import { useTheme } from '../../../context/ThemeContext';
import { useSectionContext } from '../../organisms/Section';
import LoadingIndicator from '../LoadingIndicator';
import { createResponsiveStyle } from '../../../utils/responsiveHelper';
import { Tile, TileConfig } from '../../../types/tile';
import { ImagePropsNoSource } from '../../../types/common';

const TileContext = createContext<Tile | null>(null);

Expand All @@ -18,8 +19,6 @@ export const useTileContext = () => {
return context;
};

type ImagePropsNoSource = Omit<ImageProps, 'source'>;

type BaseTileProps = {
tile: Tile;
children: ReactNode;
Expand All @@ -41,7 +40,7 @@ const BaseTileInner: FC<BaseTileProps> = ({ tile, children }) => {
theme.sizes.borderRadiusSm,
theme.sizes.borderRadiusLg,
],
maxWidth: [175, 175, 175],
maxWidth: [175, 175, 258],
});

return (
Expand All @@ -51,7 +50,6 @@ const BaseTileInner: FC<BaseTileProps> = ({ tile, children }) => {
styles.container,
{
backgroundColor: theme.surface,
borderColor: Color(theme.surface).darken(0.02).string(),
borderRadius: responsiveStyles.borderRadius,
maxWidth: responsiveStyles.maxWidth,
},
Expand All @@ -65,14 +63,38 @@ const BaseTileInner: FC<BaseTileProps> = ({ tile, children }) => {

const TileTitle: FC = () => {
const tile = useTileContext();
const { title } = tile.configuration as TileConfig & { title?: string };
return title ? <Text variant="title">{title}</Text> : null;
const { theme } = useTheme();
const { title, linkURL } = tile.configuration as TileConfig & {
title?: string;
linkURL?: string;
};

if (!title) return null;

return (
<View style={styles.titleContainer} accessibilityRole="header">
<Text variant="title" accessibilityLabel={title}>
{title}
</Text>
{linkURL && (
<Icon
name="ChevronRight"
size={16}
color={theme.derivedSurfaceText[20]}
/>
)}
</View>
);
};

const TileBody: FC = () => {
const TileBody: FC = (props) => {
const tile = useTileContext();
const { subtitle } = tile.configuration as TileConfig & { subtitle?: string };
return subtitle ? <Text variant="body">{subtitle}</Text> : null;
return subtitle ? (
<Text variant="body" {...props} accessibilityLabel={subtitle}>
{subtitle}
</Text>
) : null;
};

const TileImage: FC<ImagePropsNoSource> = (props) => {
Expand Down Expand Up @@ -113,9 +135,14 @@ const styles = StyleSheet.create({
alignItems: 'stretch',
justifyContent: 'flex-start',
position: 'relative',
borderWidth: 1,
aspectRatio: 1,
},
titleContainer: createResponsiveStyle({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: [4, 4, 8],
}),
});

export default BaseTile;
11 changes: 6 additions & 5 deletions lib/components/atoms/Icon/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as LucideIcons from "lucide-react";
import React from "react";
import { View } from "react-native";
import * as LucideIcons from 'lucide-react';
import React from 'react';
import { View } from 'react-native';

type IconName = keyof typeof LucideIcons;

Expand All @@ -13,9 +13,10 @@ type IconProps = {

const Icon: React.FC<IconProps> = ({
name,
color = "black",
color = 'black',
size = 24,
strokeWidth = 2,
...props
}) => {
const LucideIcon = LucideIcons[name];

Expand All @@ -25,7 +26,7 @@ const Icon: React.FC<IconProps> = ({
}

return (
<View>
<View {...props}>
{/* @ts-ignore */}
<LucideIcon color={color} size={size} strokeWidth={strokeWidth} />
</View>
Expand Down
3 changes: 1 addition & 2 deletions lib/components/atoms/Text/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@ export const Text: React.FC<TextProps> = ({
case 'title':
return createResponsiveStyle({
...baseStyle,
fontSize: 14,
fontSize: [14, 14, 24],
fontWeight: 'bold',
marginBottom: [4, 4, 8],
});
case 'subtitle':
return createResponsiveStyle({
Expand Down
39 changes: 25 additions & 14 deletions lib/components/organisms/BadgeTile/BadgeTile.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
import { Meta, StoryFn } from "@storybook/react";
import React from "react";
import BadgeTile from "./index";
import React from 'react';
import { StoryFn, Meta } from '@storybook/react';
import BadgeTile from './index';
import { TileHeight, TileType } from '../../../types/tile';

export default {
title: "components/organisms/BadgeTile",
title: 'components/organisms/BadgeTile',
component: BadgeTile,
} as Meta;

const Template: StoryFn<typeof BadgeTile> = (args) => <BadgeTile {...args} />;

export const Default = Template.bind({});
Default.args = {
configuration: {
badgeId: "a7a3e1f2-be70-4e5a-a4b7-0d9870c56f0d",
badge: {
id: "a7a3e1f2-be70-4e5a-a4b7-0d9870c56f0d",
name: "Top Shopper",
description:
"You’ve earned the Top Shopper badge 2 times! Last awarded on 1 Jan 2024.",
artworkUrl: "https://picsum.photos/200/300",
createdAt: "2024-08-15T13:06:14.583Z",
updatedAt: "2024-08-15T13:06:14.583Z",
tile: {
id: '1',
type: TileType.Badge,
createdAt: new Date(),
updatedAt: new Date(),
tenantId: 'tenant1',
visibilityCriteria: 'all',
tileHeight: TileHeight.Full,
configuration: {
badgeId: 'a7a3e1f2-be70-4e5a-a4b7-0d9870c56f0d',
badge: {
id: 'a7a3e1f2-be70-4e5a-a4b7-0d9870c56f0d',
name: 'Top Shopper',
description:
'You’ve earned the Top Shopper badge 2 times! Last awarded on 1 Jan 2024.',
artworkUrl:
'https://images.pexels.com/photos/1362534/pexels-photo-1362534.jpeg',
createdAt: '2024-08-15T13:06:14.583Z',
updatedAt: '2024-08-15T13:06:14.583Z',
},
},
},
};
114 changes: 76 additions & 38 deletions lib/components/organisms/BadgeTile/index.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,94 @@
import { BadgeTileConfig } from "../../../types/tile";
import { createResponsiveStyle } from "../../../utils/responsiveHelper";
import { Icon, Text, BaseTile } from "../../atoms";
import { useSectionContext } from "../Section";

import React from "react";
import { Image, StyleSheet, View } from "react-native";
import React, { FC } from 'react';
import { Image, StyleSheet, View } from 'react-native';
import { BaseTile, Icon, Text } from '../../atoms';
import { Tile, TileConfig } from '../../../types/tile';
import { useTileContext } from '../../atoms/BaseTile';
import { ImagePropsNoSource } from '../../../types/common';
import { createResponsiveStyle } from '../../../utils/responsiveHelper';
import { Badge } from '../../../types/wll';
import { useTheme } from '../../../context/ThemeContext';

type BadgeTileProps = {
configuration: BadgeTileConfig;
tile: Tile;
};

type BadgeTileComponent = FC<BadgeTileProps> & {
Title: FC;
Body: FC;
Image: FC<ImagePropsNoSource>;
};

const BadgeTile: React.FC<BadgeTileProps> = ({ configuration }) => {
const { loading } = useSectionContext();
const { badge } = configuration;
const BadgeTileInner: FC<BadgeTileProps> = ({ tile }) => {
const { theme } = useTheme();
return (
<Tile>
<View style={styles.imageContainer}>
<Image source={{ uri: badge?.artworkUrl }} style={styles.image} />
</View>
<BaseTile tile={tile}>
<BadgeTile.Image style={styles.image} />
<View style={styles.textContainer}>
<View
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
>
<Text variant="title">{badge?.name}</Text>
<Icon name="ChevronRight" size={16} color="#666" />
<View style={styles.row}>
<BadgeTile.Title />
<Icon
name="ChevronRight"
size={16}
color={theme.derivedSurfaceText[20]}
/>
</View>
<Text variant="body">{badge?.description}</Text>
<BadgeTile.Body />
</View>
</Tile>
</BaseTile>
);
};

const BadgeTileImage: FC<ImagePropsNoSource> = (props) => {
const tile = useTileContext();
const { badge } = tile.configuration as TileConfig & { badge?: Badge };
if (!badge) return null;
return <Image {...props} source={{ uri: badge.artworkUrl }} />;
};

const BadgeTileTitle: FC = (props) => {
const tile = useTileContext();
const { badge } = tile.configuration as TileConfig & { badge?: Badge };
if (!badge) return null;
return (
<Text variant="title" {...props}>
{badge.name}
</Text>
);
};

const BadgeTileBody: FC = (props) => {
const tile = useTileContext();
const { badge } = tile.configuration as TileConfig & { badge?: Badge };
if (!badge) return null;
return (
<Text variant="body" {...props}>
{badge.description}
</Text>
);
};

export const BadgeTile = BadgeTileInner as BadgeTileComponent;

BadgeTile.Image = BadgeTileImage;
BadgeTile.Title = BadgeTileTitle;
BadgeTile.Body = BadgeTileBody;

const styles = StyleSheet.create({
image: {
width: "100%",
height: "100%",
position: "absolute",
},
imageContainer: {
width: "100%",
flex: 1,
position: "relative",
overflow: "hidden",
},
textContainer: createResponsiveStyle({
padding: [8, 10, 12],
paddingHorizontal: [8, 8, 12],
flex: 1,
}),
image: createResponsiveStyle({
width: '100%',
flexBasis: '50%',
marginBottom: [8, 8, 12],
}),
row: createResponsiveStyle({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: [4, 4, 8],
}),
});

export default BadgeTile;
24 changes: 14 additions & 10 deletions lib/components/organisms/ContentTile/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,22 @@ import { StyleSheet, View } from 'react-native';
import { Tile } from '../../../types/tile';
import BaseTile from '../../atoms/BaseTile';
import { createResponsiveStyle } from '../../../utils/responsiveHelper';
import { Icon } from '../../atoms';
import { useTheme } from '../../../context/ThemeContext';

type ContentTileProps = {
tile: Tile;
children?: ReactNode;
};

const ContentTile: FC<ContentTileProps> = ({ children, tile }) => {
const ContentTile: FC<ContentTileProps> = ({ tile }) => {
return (
<BaseTile tile={tile}>
{children || (
<>
<BaseTile.Image style={styles.image} />
<View style={styles.textContainer}>
<BaseTile.Title />
<BaseTile.Body />
</View>
</>
)}
<BaseTile.Image style={styles.image} />
<View style={styles.textContainer}>
<BaseTile.Title />
<BaseTile.Body />
</View>
</BaseTile>
);
};
Expand All @@ -33,6 +31,12 @@ const styles = StyleSheet.create({
width: '100%',
marginBottom: [8, 8, 12],
}),
row: createResponsiveStyle({
flexDirection: 'row',
alignItems: 'center',
marginBottom: [4, 4, 8],
justifyContent: 'space-between',
}),
});

export default ContentTile;
3 changes: 3 additions & 0 deletions lib/types/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { ImageProps } from 'react-native';

export type ImagePropsNoSource = Omit<ImageProps, 'source'>;
Loading

0 comments on commit db36922

Please sign in to comment.