Skip to content

Commit

Permalink
OPHJOD-1253: Add horizontal variant of MediaCard
Browse files Browse the repository at this point in the history
  • Loading branch information
jussi-siren committed Feb 13, 2025
1 parent b018884 commit ddb06fa
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 44 deletions.
99 changes: 67 additions & 32 deletions lib/components/MediaCard/MediaCard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const Default: Story = {
},
docs: {
description: {
story: 'MediaCard where there is no favorite-button available',
story: 'Vertical MediaCard where there is no favorite-button available',
},
},
},
Expand All @@ -36,6 +36,28 @@ export const Default: Story = {
},
};

export const Horizontal: Story = {
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/design/6M2LrpSCcB0thlFDaQAI2J/cx_jod_client?node-id=2217-8529',
},
docs: {
description: {
story: 'Horizontal MediaCard where there is no favorite-button available',
},
},
},
args: {
variant: 'horizontal',
imageSrc,
imageAlt: 'Woman standing in front of a colourful wall',
label: 'Tulevaisuusmatka',
description: 'Tulevaisuusmatka on koulutus, joka auttaa sinua löytämään oman polkusi ja tavoitteesi.',
tags: ['Taglorem', 'Loremtag', 'Nonutag'],
},
};

export const Multiple: Story = {
render: (args) => {
const longTitle = 'Otsikko joka aina vain jatkuu ja jatkuu ja rivittyy';
Expand All @@ -58,7 +80,7 @@ export const Multiple: Story = {
},
docs: {
description: {
story: `Group of MediaCards with different lengths of title, description and tags.`,
story: `Group of Vertical MediaCards with different lengths of title, description and tags.`,
},
},
},
Expand All @@ -71,37 +93,50 @@ export const Multiple: Story = {
},
};

export const AbleToBeFavorited: Story = {
parameters: {
docs: {
description: {
story: 'MediaCard where the favorite status can be toggled.',
const getAbleToBeFavorite = (description: string, variant: 'horizontal' | 'vertical'): Story => {
return {
parameters: {
docs: {
description: {
story: description,
},
},
design: {
type: 'figma',
url: 'https://www.figma.com/design/6M2LrpSCcB0thlFDaQAI2J/cx_jod_client?node-id=2217-8529',
},
},
design: {
type: 'figma',
url: 'https://www.figma.com/design/6M2LrpSCcB0thlFDaQAI2J/cx_jod_client?node-id=2217-8529',
render: (args) => {
const [isFavorite, setFavorite] = useState(true);
return (
<MediaCard
{...args}
favoriteLabel={isFavorite ? 'Poista suosikeista' : 'Lisää suosikkeihin'}
isFavorite={isFavorite}
onFavoriteClick={() => setFavorite(!isFavorite)}
/>
);
},
},
render: (args) => {
const [isFavorite, setFavorite] = useState(true);
return (
<MediaCard
{...args}
favoriteLabel={isFavorite ? 'Poista suosikeista' : 'Lisää suosikkeihin'}
isFavorite={isFavorite}
onFavoriteClick={() => setFavorite(!isFavorite)}
/>
);
},
args: {
imageSrc,
imageAlt: 'Woman standing in front of a colourful wall',
label: 'Tulevaisuusmatka',
description: 'Tulevaisuusmatka on koulutus, joka auttaa sinua löytämään oman polkusi ja tavoitteesi.',
tags: ['Taglorem', 'Loremtag', 'Nonutag'],
isFavorite: true,
onFavoriteClick: fn(),
favoriteLabel: 'Poista suosikeista',
},
args: {
variant,
imageSrc,
imageAlt: 'Woman standing in front of a colourful wall',
label: 'Tulevaisuusmatka',
description: 'Tulevaisuusmatka on koulutus, joka auttaa sinua löytämään oman polkusi ja tavoitteesi.',
tags: ['Taglorem', 'Loremtag', 'Nonutag'],
isFavorite: true,
onFavoriteClick: fn(),
favoriteLabel: 'Poista suosikeista',
},
};
};

export const AbleToBeFavoritedVertical: Story = getAbleToBeFavorite(
'Vertical MediaCard where the favorite status can be toggled.',
'vertical',
);

export const AbleToBeFavoritedHorizontal: Story = getAbleToBeFavorite(
'Horizontal MediaCard where the favorite status can be toggled.',
'horizontal',
);
25 changes: 24 additions & 1 deletion lib/components/MediaCard/MediaCard.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import { MediaCard } from './MediaCard';

describe('MediaCard', () => {
it('renders the MediaCard component with vertical variant', () => {
render(
<MediaCard
variant="vertical"
imageSrc="vertical.jpg"
imageAlt="Image for the vertical"
label="Vertical"
Expand All @@ -21,6 +22,28 @@ describe('MediaCard', () => {
expect(screen.getByText('tag2')).toBeInTheDocument();
});

it('renders the MediaCard component with horizontal variant', () => {
vi.mock('../../hooks/useMediaQueries', () => ({
useMediaQueries: vi.fn().mockReturnValue({ sm: true, md: false, lg: false, xl: false }),
}));
render(
<MediaCard
variant="horizontal"
imageSrc="horizontal.jpg"
imageAlt="Image for the horizontal"
label="Horizontal"
description="Horizontal description"
tags={['tag1', 'tag2']}
/>,
);

expect(screen.getByAltText('Image for the horizontal')).toBeInTheDocument();
expect(screen.getByText('Horizontal')).toBeInTheDocument();
expect(screen.getByText('Horizontal description')).toBeInTheDocument();
expect(screen.getByText('tag1')).toBeInTheDocument();
expect(screen.getByText('tag2')).toBeInTheDocument();
});

it('renders the MediaCard component with default vertical variant', () => {
const { container } = render(
<MediaCard
Expand Down
90 changes: 83 additions & 7 deletions lib/components/MediaCard/MediaCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { MdFavorite, MdFavoriteBorder } from 'react-icons/md';
import { useMediaQueries } from '../../hooks/useMediaQueries';

type LinkComponent =
| {
Expand Down Expand Up @@ -28,6 +29,7 @@ type FavoriteButtonProps =
};

export type MediaCardProps = {
variant?: 'vertical' | 'horizontal';
imageSrc: string;
imageAlt: string;
label: string;
Expand All @@ -36,6 +38,14 @@ export type MediaCardProps = {
} & LinkComponent &
FavoriteButtonProps;

type MediaCardImplProps = {
imageSrc: string;
imageAlt: string;
label: string;
description: string;
children: React.ReactNode;
} & LinkComponent;

const Tag = ({ label }: { label: string }) => {
return <li className="ds:px-2 ds:pb-2 ds:last:pr-0">{label}</li>;
};
Expand Down Expand Up @@ -81,6 +91,7 @@ const FavoriteButton = ({ isFavorite, favoriteLabel, onFavoriteClick }: Favorite
};

export const MediaCard = ({
variant = 'vertical',
imageSrc,
imageAlt,
label,
Expand All @@ -92,6 +103,37 @@ export const MediaCard = ({
onFavoriteClick,
favoriteLabel,
}: MediaCardProps) => {
const favoriteButtonAndTags = (
<>
{isFavorite !== undefined && (
<FavoriteButton isFavorite={isFavorite} favoriteLabel={favoriteLabel} onFavoriteClick={onFavoriteClick} />
)}
<ul className="ds:text-attrib-value ds:flex ds:flex-row ds:divide-x ds:divide-secondary-5 ds:flex-wrap ds:text-accent ds:pt-3">
{tags.filter(Boolean).map((tag) => (
<Tag key={tag} label={tag} />
))}
</ul>
</>
);
const linkProps = to && Link ? { to, linkComponent: Link } : {};
const MediaCardComponent = variant === 'vertical' ? MediaCardVertical : MediaCardHorizontal;

return (
<MediaCardComponent imageSrc={imageSrc} imageAlt={imageAlt} label={label} description={description} {...linkProps}>
{favoriteButtonAndTags}
</MediaCardComponent>
);
};

const MediaCardVertical = ({
imageSrc,
imageAlt,
label,
description,
linkComponent: Link,
to,
children,
}: MediaCardImplProps) => {
const variantImageClassNames = 'ds:object-cover ds:min-h-[147px]';
const labelRef = React.useRef<HTMLDivElement>(null);
const SINGLE_LINE_LABEL_HEIGHT = 27;
Expand Down Expand Up @@ -120,14 +162,48 @@ export const MediaCard = ({
</div>
<div className={`ds:text-body-sm-mobile ds:sm:text-body-sm ${lineClampClassNames}`}>{description}</div>
</div>
{isFavorite !== undefined && (
<FavoriteButton isFavorite={isFavorite} favoriteLabel={favoriteLabel} onFavoriteClick={onFavoriteClick} />
{children}
</div>
</LinkOrDiv>
);
};

const MediaCardHorizontal = ({
imageSrc,
imageAlt,
label,
description,
linkComponent: Link,
to,
children,
}: MediaCardImplProps) => {
const { sm } = useMediaQueries();

return (
<LinkOrDiv to={to} linkComponent={Link} className="ds:relative ds:flex ds:flex-row ds:min-h-[137px] ds:w-full">
<div className="ds:shrink-0">
{imageSrc ? (
sm && (
<img
className="ds:sm:w-[193px] ds:lg:w-[255px] ds:sm:min-w-full ds:sm:min-h-full ds:sm:h-0 ds:object-cover"
src={imageSrc}
alt={imageAlt}
/>
)
) : (
<div className={`ds:sm:w-[193px] ds:lg:w-[255px] ds:sm:min-w-full ds:sm:min-h-full ds:bg-secondary-5`}></div>
)}
<ul className="ds:text-attrib-value ds:flex ds:flex-row ds:divide-x ds:divide-secondary-5 ds:flex-wrap ds:text-accent ds:pt-3">
{tags.filter(Boolean).map((tag) => (
<Tag key={tag} label={tag} />
))}
</ul>
</div>
<div className="ds:px-5 ds:pt-4 ds:pb-5 ds:text-black ds:flex ds:flex-col ds:justify-between ds:flex-nowrap">
<div className="ds:gap-3 ds:flex ds:flex-col">
<div className="ds:text-heading-3-mobile ds:sm:text-heading-3 ds:line-clamp-3 ds:sm:line-clamp-2">
{label}
</div>
<div className="ds:text-body-sm-mobile ds:sm:text-body-sm ds:line-clamp-3 ds:sm:line-clamp-2">
{description}
</div>
</div>
{children}
</div>
</LinkOrDiv>
);
Expand Down
8 changes: 4 additions & 4 deletions lib/hooks/useMediaQueries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ const useMediaQuery = (query: string) => {

export const useMediaQueries = () => {
const { sm, md, lg, xl } = {
sm: styles.getPropertyValue('--breakpoint-sm'),
md: styles.getPropertyValue('--breakpoint-md'),
lg: styles.getPropertyValue('--breakpoint-lg'),
xl: styles.getPropertyValue('--breakpoint-xl'),
sm: styles.getPropertyValue('--ds-breakpoint-sm'),
md: styles.getPropertyValue('--ds-breakpoint-md'),
lg: styles.getPropertyValue('--ds-breakpoint-lg'),
xl: styles.getPropertyValue('--ds-breakpoint-xl'),
};
return {
sm: useMediaQuery(`(min-width: ${sm})`),
Expand Down

0 comments on commit ddb06fa

Please sign in to comment.