Skip to content

Commit

Permalink
Add icon support to category cards (#191)
Browse files Browse the repository at this point in the history
* Use icons in lieu category image
* Add tests on Card
  • Loading branch information
terotik authored Oct 13, 2023
1 parent 184d281 commit 98a2e9b
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 54 deletions.
106 changes: 74 additions & 32 deletions components/common/Card.js → components/common/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import PropTypes from 'prop-types';
import SVG from 'react-inlinesvg';
import { Card as BSCard, CardBody } from 'reactstrap';
import styled from 'styled-components';
import { transparentize } from 'polished';
Expand Down Expand Up @@ -50,14 +49,24 @@ const StyledCard = styled(BSCard)`
}
`;

const ImgArea = styled.div`
const ImgArea = styled.div<{ colorEffect?: string }>`
height: 9rem;
position: relative;
display: flex;
align-items: center;
justify-content: center;
background-color: ${(props) => props.colorEffect || props.theme.brandDark};
border-bottom: ${(props) => (props.colorEffect ? '6px' : '0')} solid
${(props) => props.colorEffect};
@media (min-width: ${(props) => props.theme.breakpointMd}) {
height: 8rem;
}
`;

const ImgBg = styled.div`
const ImgBg = styled.div<{ background: string; imageAlign: string }>`
height: 9rem;
flex: 1 1 100%;
background-image: url(${(props) => props.background});
background-position: ${(props) => props.imageAlign};
background-size: cover;
Expand All @@ -67,51 +76,84 @@ const ImgBg = styled.div`
}
`;

const Card = (props) => {
const SvgIcon = styled(SVG)`
width: ${(props) => props.theme.spaces.s800};
fill: white;
`;

const BitmapIcon = styled.div<{ imageSrc: string }>`
width: ${(props) => props.theme.spaces.s800};
height: ${(props) => props.theme.spaces.s800};
background-image: url(${(props) => props.imageSrc || 'none'});
background-size: cover;
background-position: center center;
`;

interface CardProps {
imageUrl?: string;
imageAlign?: string;
imageType?: 'svgIcon' | 'bitmapIcon' | 'image';
colorEffect?: string;
negative?: boolean;
customColor?: string;
children: React.ReactNode;
outline?: boolean;
}

const Card = (props: CardProps) => {
const {
imageUrl,
colorEffect,
imageAlign,
imageAlign = 'center center',
imageType = 'image',
negative,
customColor,
children,
outline,
} = props;

/*
Support svgIcon, bitmapIcon, image as cards main image
*/
const ImageComponent = () => {
if (imageType === 'svgIcon') {
return (
<ImgArea colorEffect={colorEffect} data-testid="svg-icon">
{imageUrl && <SvgIcon src={imageUrl} />}
</ImgArea>
);
}
if (imageType === 'bitmapIcon') {
return (
<ImgArea colorEffect={colorEffect} data-testid="bitmap-icon">
{imageUrl && <BitmapIcon imageSrc={imageUrl} />}
</ImgArea>
);
}
if (imageType === 'image' && imageUrl) {
return (
<ImgArea colorEffect={colorEffect}>
<ImgBg
background={imageUrl}
imageAlign={imageAlign}
data-testid="image-bg"
/>
</ImgArea>
);
}
return null;
};

return (
<StyledCard
className={`${negative && 'negative'} ${outline && 'outline'}`}
customcolor={customColor}
data-testid='card'
data-testid="card"
>
{/* TODO: maybe animate transition */}
{imageUrl && (
<ImgArea colorEffect={colorEffect}>
<ImgBg data-testid='image-bg' background={imageUrl} imageAlign={imageAlign} />
</ImgArea>
)}
<ImageComponent />
<CardBody>{children}</CardBody>
</StyledCard>
);
};

Card.defaultProps = {
imageUrl: '',
imageAlign: 'center',
colorEffect: undefined,
negative: false,
customColor: '',
outline: false,
};

Card.propTypes = {
imageUrl: PropTypes.string,
imageAlign: PropTypes.string,
colorEffect: PropTypes.string,
negative: PropTypes.bool,
customColor: PropTypes.string,
children: PropTypes.element.isRequired,
outline: PropTypes.bool,
};

export default Card;
60 changes: 53 additions & 7 deletions components/contentblocks/CategoryListBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ const CATEGORY_FRAGMENT = gql`
...MultiUseImageFragment
}
color
iconSvgUrl
iconImage {
rendition(size: "400x400", crop: false) {
src
}
}
categoryPage {
title
urlPath
Expand Down Expand Up @@ -122,6 +128,12 @@ export type CategoryListBlockCategory = {
id: string;
image?: MultiUseImageFragmentFragment | null;
color?: string | null;
iconSvgUrl?: string | null;
iconImage?: {
rendition: {
src: string;
};
};
identifier: string;
name: string;
shortDescription?: string;
Expand Down Expand Up @@ -153,6 +165,43 @@ const CategoryListBlock = (props: CategoryListBlockProps) => {
categories = fallbackCategories,
} = props;

/*
Determine what image to use on category card
If category has no own image but has icon use icon on colored bg
If category has no own color use bradDark color as background
If category has no own image and no icon use fallback image
If category has own image use that
*/
type CardImageType = (category: CategoryListBlockCategory) => {
type: 'image' | 'svgIcon' | 'bitmapIcon';
src: string | undefined;
alignment: string;
};

const getCardImage: CardImageType = (category) => {
const categryImageSrc = category.image?.small?.src;

if (!categryImageSrc && category.iconSvgUrl) {
return {
type: 'svgIcon',
src: category.iconSvgUrl,
alignment: 'center',
};
}
if (!categryImageSrc && category.iconImage) {
return {
type: 'bitmapIcon',
src: category.iconImage?.rendition?.src,
alignment: 'center',
};
} else
return {
type: 'image',
src: categryImageSrc || fallbackImage?.small?.src,
alignment: getBgImageAlignment(category.image || fallbackImage),
};
};

return (
<CategoryListSection id={id}>
<Container>
Expand All @@ -175,13 +224,10 @@ const CategoryListBlock = (props: CategoryListBlockProps) => {
<Link href={cat.categoryPage.urlPath}>
<a className="card-wrapper">
<Card
imageUrl={
cat.image?.small?.src || fallbackImage?.small?.src
}
imageAlign={getBgImageAlignment(
cat.image || fallbackImage
)}
colorEffect={cat.color}
imageUrl={getCardImage(cat).src}
imageAlign={getCardImage(cat).alignment}
imageType={getCardImage(cat).type}
colorEffect={cat.color ?? undefined}
>
<div>
<CardHeader className="card-title">
Expand Down
51 changes: 36 additions & 15 deletions tests/common/Card.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,56 @@ import { screen } from '@testing-library/react';

describe('Card Component', () => {
it('renders children correctly', () => {
render(<Card><div>Test Content</div></Card>);
render(
<Card>
<div>Test Content</div>
</Card>
);

const content = screen.getByText('Test Content');
expect(content).toBeInTheDocument();
});

it('renders with an image when imageUrl is provided', () => {
const imageUrl = "image.jpg";
const imageUrl = 'image.jpg';

render(
<Card imageUrl={imageUrl}>
<div>Test Content</div>
</Card>
);

const image = screen.getByTestId('image-bg');;
const image = screen.getByTestId('image-bg');
expect(image).toBeInTheDocument();
});
});

it('renders with a svg icon when imageType === svgIcon', () => {
const imageType = 'svgIcon';
const imageUrl = 'image.svg';

render(
<Card imageUrl={imageUrl} imageType={imageType}>
<div>Test Content</div>
</Card>
);

const icon = screen.getByTestId('svg-icon');
expect(icon).toBeInTheDocument();
});

it('renders with a bitmap icon when imageType === bitmapIcon', () => {
const imageType = 'bitmapIcon';
const imageUrl = 'image.png';

render(
<Card imageUrl={imageUrl} imageType={imageType}>
<div>Test Content</div>
</Card>
);

const icon = screen.getByTestId('bitmap-icon');
expect(icon).toBeInTheDocument();
});

it('uses customcolor when provided', () => {
const customColorValue = 'rgb(212, 235, 255)';
Expand All @@ -45,16 +77,5 @@ describe('Card Component', () => {
);
const card = screen.getByTestId('card');
expect(card).toHaveStyle(`background-color: ${defaultColor}`);

});
});










0 comments on commit 98a2e9b

Please sign in to comment.