From db36922943e8b04b81442c0f8379933b497342c1 Mon Sep 17 00:00:00 2001 From: graemehouston Date: Sat, 31 Aug 2024 22:00:25 +0100 Subject: [PATCH] Create BadgeTile --- lib/components/atoms/BaseTile/index.tsx | 47 ++++++-- lib/components/atoms/Icon/index.tsx | 11 +- lib/components/atoms/Text/index.tsx | 3 +- .../organisms/BadgeTile/BadgeTile.stories.tsx | 39 +++--- lib/components/organisms/BadgeTile/index.tsx | 114 ++++++++++++------ .../organisms/ContentTile/index.tsx | 24 ++-- lib/types/common.ts | 3 + lib/types/tile.ts | 65 +--------- lib/types/wll.ts | 62 ++++++++++ 9 files changed, 226 insertions(+), 142 deletions(-) create mode 100644 lib/types/common.ts create mode 100644 lib/types/wll.ts diff --git a/lib/components/atoms/BaseTile/index.tsx b/lib/components/atoms/BaseTile/index.tsx index 9fc468e..9b589bc 100644 --- a/lib/components/atoms/BaseTile/index.tsx +++ b/lib/components/atoms/BaseTile/index.tsx @@ -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(null); @@ -18,8 +19,6 @@ export const useTileContext = () => { return context; }; -type ImagePropsNoSource = Omit; - type BaseTileProps = { tile: Tile; children: ReactNode; @@ -41,7 +40,7 @@ const BaseTileInner: FC = ({ tile, children }) => { theme.sizes.borderRadiusSm, theme.sizes.borderRadiusLg, ], - maxWidth: [175, 175, 175], + maxWidth: [175, 175, 258], }); return ( @@ -51,7 +50,6 @@ const BaseTileInner: FC = ({ tile, children }) => { styles.container, { backgroundColor: theme.surface, - borderColor: Color(theme.surface).darken(0.02).string(), borderRadius: responsiveStyles.borderRadius, maxWidth: responsiveStyles.maxWidth, }, @@ -65,14 +63,38 @@ const BaseTileInner: FC = ({ tile, children }) => { const TileTitle: FC = () => { const tile = useTileContext(); - const { title } = tile.configuration as TileConfig & { title?: string }; - return title ? {title} : null; + const { theme } = useTheme(); + const { title, linkURL } = tile.configuration as TileConfig & { + title?: string; + linkURL?: string; + }; + + if (!title) return null; + + return ( + + + {title} + + {linkURL && ( + + )} + + ); }; -const TileBody: FC = () => { +const TileBody: FC = (props) => { const tile = useTileContext(); const { subtitle } = tile.configuration as TileConfig & { subtitle?: string }; - return subtitle ? {subtitle} : null; + return subtitle ? ( + + {subtitle} + + ) : null; }; const TileImage: FC = (props) => { @@ -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; diff --git a/lib/components/atoms/Icon/index.tsx b/lib/components/atoms/Icon/index.tsx index f29904e..7ef1e76 100644 --- a/lib/components/atoms/Icon/index.tsx +++ b/lib/components/atoms/Icon/index.tsx @@ -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; @@ -13,9 +13,10 @@ type IconProps = { const Icon: React.FC = ({ name, - color = "black", + color = 'black', size = 24, strokeWidth = 2, + ...props }) => { const LucideIcon = LucideIcons[name]; @@ -25,7 +26,7 @@ const Icon: React.FC = ({ } return ( - + {/* @ts-ignore */} diff --git a/lib/components/atoms/Text/index.tsx b/lib/components/atoms/Text/index.tsx index 0a9ae8d..c6c2123 100644 --- a/lib/components/atoms/Text/index.tsx +++ b/lib/components/atoms/Text/index.tsx @@ -42,9 +42,8 @@ export const Text: React.FC = ({ case 'title': return createResponsiveStyle({ ...baseStyle, - fontSize: 14, + fontSize: [14, 14, 24], fontWeight: 'bold', - marginBottom: [4, 4, 8], }); case 'subtitle': return createResponsiveStyle({ diff --git a/lib/components/organisms/BadgeTile/BadgeTile.stories.tsx b/lib/components/organisms/BadgeTile/BadgeTile.stories.tsx index 8f79067..16c99cf 100644 --- a/lib/components/organisms/BadgeTile/BadgeTile.stories.tsx +++ b/lib/components/organisms/BadgeTile/BadgeTile.stories.tsx @@ -1,9 +1,10 @@ -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; @@ -11,16 +12,26 @@ const Template: StoryFn = (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', + }, }, }, }; diff --git a/lib/components/organisms/BadgeTile/index.tsx b/lib/components/organisms/BadgeTile/index.tsx index 67b54d9..7f72b81 100644 --- a/lib/components/organisms/BadgeTile/index.tsx +++ b/lib/components/organisms/BadgeTile/index.tsx @@ -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 & { + Title: FC; + Body: FC; + Image: FC; }; -const BadgeTile: React.FC = ({ configuration }) => { - const { loading } = useSectionContext(); - const { badge } = configuration; +const BadgeTileInner: FC = ({ tile }) => { + const { theme } = useTheme(); return ( - - - - + + - - {badge?.name} - + + + - {badge?.description} + - + + ); +}; + +const BadgeTileImage: FC = (props) => { + const tile = useTileContext(); + const { badge } = tile.configuration as TileConfig & { badge?: Badge }; + if (!badge) return null; + return ; +}; + +const BadgeTileTitle: FC = (props) => { + const tile = useTileContext(); + const { badge } = tile.configuration as TileConfig & { badge?: Badge }; + if (!badge) return null; + return ( + + {badge.name} + ); }; +const BadgeTileBody: FC = (props) => { + const tile = useTileContext(); + const { badge } = tile.configuration as TileConfig & { badge?: Badge }; + if (!badge) return null; + return ( + + {badge.description} + + ); +}; + +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; diff --git a/lib/components/organisms/ContentTile/index.tsx b/lib/components/organisms/ContentTile/index.tsx index 301f7be..c79c17b 100644 --- a/lib/components/organisms/ContentTile/index.tsx +++ b/lib/components/organisms/ContentTile/index.tsx @@ -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 = ({ children, tile }) => { +const ContentTile: FC = ({ tile }) => { return ( - {children || ( - <> - - - - - - - )} + + + + + ); }; @@ -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; diff --git a/lib/types/common.ts b/lib/types/common.ts new file mode 100644 index 0000000..426a2e0 --- /dev/null +++ b/lib/types/common.ts @@ -0,0 +1,3 @@ +import { ImageProps } from 'react-native'; + +export type ImagePropsNoSource = Omit; diff --git a/lib/types/tile.ts b/lib/types/tile.ts index 7c21b94..1f7bdb9 100644 --- a/lib/types/tile.ts +++ b/lib/types/tile.ts @@ -1,3 +1,5 @@ +import { Badge, RewardCategory, TierType } from './wll'; + export enum CtaAction { sameWindow = 'SAME_WINDOW', newWindow = 'NEW_WINDOW', @@ -29,69 +31,6 @@ export enum TileHeight { Full = 'FULL', } -type Badge = { - id: string; - name: string; - description: string; - artworkUrl: string; - createdAt: string; - updatedAt: string; -}; - -export class RewardCategory { - name?: string; - priority?: number; - type?: string; - id?: string; - createdAt?: string; - updatedAt?: string; - description?: string; - metadata?: string; - pictureUrl?: string; - rewards?: Reward[]; - parent?: string; -} - -export class Reward { - id?: string; - createdAt?: string; - updatedAt?: string; - name?: string; - pictureUrl?: string; - price?: number; - priority?: number; - availability?: Availability; - purchasable?: boolean; - tier?: string; - summary?: string; - redemptionMessage?: string; - visibilityCriteria?: string; -} -type Availability = { - start: Date; - end: Date; -}; -type Effectivity = { - start: Date; - end: Date; -}; - -export type TierType = { - id: string; - name: string; - description: string; - artworkUrl: string; - priority: number; - pointsRequirement: number; - calculation: string; - accumulationPeriod: string; - effectivity: Effectivity; - createdAt: string; - updatedAt: string; - earnedPoints: number; - attained: boolean; -}; - export class BannerTileConfig { imageUrl?: string; title?: string; diff --git a/lib/types/wll.ts b/lib/types/wll.ts new file mode 100644 index 0000000..bd76c6d --- /dev/null +++ b/lib/types/wll.ts @@ -0,0 +1,62 @@ +export type Badge = { + id: string; + name: string; + description: string; + artworkUrl: string; + createdAt: string; + updatedAt: string; +}; + +export class RewardCategory { + name?: string; + priority?: number; + type?: string; + id?: string; + createdAt?: string; + updatedAt?: string; + description?: string; + metadata?: string; + pictureUrl?: string; + rewards?: Reward[]; + parent?: string; +} + +export class Reward { + id?: string; + createdAt?: string; + updatedAt?: string; + name?: string; + pictureUrl?: string; + price?: number; + priority?: number; + availability?: Availability; + purchasable?: boolean; + tier?: string; + summary?: string; + redemptionMessage?: string; + visibilityCriteria?: string; +} +type Availability = { + start: Date; + end: Date; +}; +type Effectivity = { + start: Date; + end: Date; +}; + +export type TierType = { + id: string; + name: string; + description: string; + artworkUrl: string; + priority: number; + pointsRequirement: number; + calculation: string; + accumulationPeriod: string; + effectivity: Effectivity; + createdAt: string; + updatedAt: string; + earnedPoints: number; + attained: boolean; +};