Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add icon support to category cards #191

Merged
merged 2 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}`);

});
});










Loading