Skip to content

Commit f539ae1

Browse files
Add card component
1 parent c03d1b6 commit f539ae1

File tree

6 files changed

+279
-0
lines changed

6 files changed

+279
-0
lines changed

public/images/default-avatar.png

31.3 KB
Loading

src/lib/Card/Card.scss

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
@import "../../styles/variables";
2+
3+
.card {
4+
display: flex;
5+
flex-direction: column;
6+
background: $color__n-85;
7+
border-radius: $border-radius__m;
8+
9+
&--transparent {
10+
background: transparent;
11+
}
12+
13+
&--outlined {
14+
border: 1px solid $color__n-60;
15+
}
16+
17+
&--bottom-aligned {
18+
.card__body {
19+
align-self: flex-end;
20+
}
21+
}
22+
23+
&__main {
24+
display: flex;
25+
flex-direction: row;
26+
align-items: flex-start;
27+
justify-content: space-evenly;
28+
padding: $spacing__m;
29+
}
30+
31+
&__image {
32+
height: 5.625rem;
33+
width: 5.625rem;
34+
border-radius: 50%;
35+
overflow: hidden;
36+
display: flex;
37+
justify-content: center;
38+
align-items: center;
39+
object-fit: cover;
40+
aspect-ratio: 1/1;
41+
42+
img {
43+
height: 100%;
44+
width: 100%;
45+
}
46+
47+
&--l {
48+
height: 9rem;
49+
width: 9rem;
50+
}
51+
52+
&--s {
53+
height: 4rem;
54+
width: 4rem;
55+
}
56+
57+
&--radius-l {
58+
border-radius: $border-radius__l;
59+
}
60+
61+
&--radius-m {
62+
border-radius: $border-radius__m;
63+
}
64+
65+
&--radius-s {
66+
border-radius: $border-radius__s;
67+
}
68+
}
69+
70+
&__title {
71+
color: $color__white;
72+
font-size: 1.3rem;
73+
margin-bottom: 0.3em;
74+
font-weight: 700;
75+
line-height: 120%;
76+
77+
a {
78+
color: $color__white;
79+
}
80+
}
81+
82+
&__body {
83+
color: $color__n-10;
84+
font-size: 1.125rem;
85+
display: flex;
86+
flex-direction: column;
87+
margin: 0 $spacing__m
88+
}
89+
90+
&__footer {
91+
color: $color__n-10;
92+
border-top: 1px solid $color__n-60;
93+
display: flex;
94+
align-items: center;
95+
justify-content: space-between;
96+
padding: 0;
97+
}
98+
99+
&__footer-link-wrapper {
100+
padding: 0;
101+
102+
a {
103+
color: $color__n-10;
104+
border-left: 1px solid $color__n-60;
105+
padding: $spacing__s $spacing__m;
106+
text-decoration: none;
107+
margin: -1px 0;
108+
109+
&:hover {
110+
color: $color__white;
111+
}
112+
}
113+
}
114+
115+
&__footer-details {
116+
padding: $spacing__s $spacing__m;
117+
margin: 0;
118+
justify-self: flex-start;
119+
}
120+
}

src/lib/Card/Card.stories.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Meta, StoryObj } from "@storybook/react";
2+
import React from "react";
3+
import { Card } from "./Card";
4+
import {
5+
CardImageBorderRadius,
6+
CardImageSize,
7+
CardProps,
8+
CardVariant,
9+
} from "./Card.types";
10+
11+
const meta: Meta<typeof Card> = {
12+
component: Card,
13+
title: "Card",
14+
tags: ["autodocs"],
15+
};
16+
export default meta;
17+
18+
type Story = StoryObj<typeof Card>;
19+
20+
export const SampleComponent: Story = (args: CardProps) => {
21+
return <Card {...args} />;
22+
};
23+
24+
SampleComponent.parameters = {
25+
docs: {
26+
canvas: { sourceState: "shown" },
27+
story: { height: "500px" },
28+
},
29+
};
30+
31+
SampleComponent.args = {
32+
children: <div style={{ width: "300px" }}>Team lead</div>,
33+
title: "MangoBurger",
34+
imageSize: CardImageSize.LARGE,
35+
image: <img src="/images/default-avatar.png" />,
36+
imageBorderRadius: CardImageBorderRadius.MEDIUM,
37+
cta: { text: "Follow", onClick: () => console.log("follow") },
38+
variants: [
39+
CardVariant.TRANSPARENT,
40+
CardVariant.OUTLINED,
41+
CardVariant.BOTTOM_ALIGNED,
42+
],
43+
footerDetails: "Member since 9/24/2020",
44+
footerLinks: [<a href="/">Home</a>, <a href="/">View</a>],
45+
};

src/lib/Card/Card.tsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import clsx from "clsx";
2+
import React from "react";
3+
import { CardImageBorderRadius, CardImageSize, CardProps } from "./Card.types";
4+
import { Button } from "../Button";
5+
import { ButtonSize, ButtonType, ButtonVariant } from "../Button/Button.types";
6+
import "./Card.scss";
7+
8+
export const Card: React.FC<CardProps> = ({
9+
children,
10+
className,
11+
variants,
12+
footerDetails,
13+
footerLinks,
14+
imageSize = CardImageSize.MEDIUM,
15+
image,
16+
imageBorderRadius = CardImageBorderRadius.CIRCLE,
17+
title,
18+
cta,
19+
}) => {
20+
return (
21+
<div
22+
className={clsx(
23+
"card",
24+
className && className,
25+
variants && variants.map((variant) => `card--${variant}`)
26+
)}
27+
>
28+
{/* Main section of card */}
29+
<div className="card__main">
30+
<div
31+
className={`card__image card__image--${imageSize} card__image--radius-${imageBorderRadius}`}
32+
>
33+
{image}
34+
</div>
35+
{/* body */}
36+
<div className="card__body">
37+
{title && <h2 className="card__title">{title}</h2>}
38+
{children}
39+
</div>
40+
{/* CTA */}
41+
{cta && (
42+
<Button
43+
onClick={cta.onClick}
44+
label={cta.text}
45+
variant={ButtonVariant.SECONDARY}
46+
type={ButtonType.BUTTON}
47+
size={ButtonSize.NARROW}
48+
/>
49+
)}
50+
</div>
51+
{/* Footer */}
52+
{(footerDetails || footerLinks) && (
53+
<footer className="card__footer">
54+
{footerDetails && (
55+
<p className="card__footer-details">{footerDetails}</p>
56+
)}
57+
{footerLinks && (
58+
<div className="card__footer-link-wrapper">
59+
{footerLinks.map((link) => link)}
60+
</div>
61+
)}
62+
</footer>
63+
)}
64+
</div>
65+
);
66+
};

src/lib/Card/Card.types.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { MouseEventHandler, ReactElement, ReactNode } from "react";
2+
3+
export enum CardVariant {
4+
TRANSPARENT = "transparent",
5+
OUTLINED = "outlined",
6+
COMPACT = "compact",
7+
BOTTOM_ALIGNED = "bottom-aligned",
8+
}
9+
10+
export enum CardImageSize {
11+
LARGE = "l",
12+
MEDIUM = "m",
13+
SMALL = "s",
14+
}
15+
16+
export enum CardImageBorderRadius {
17+
CIRCLE = "circle",
18+
LARGE = "l",
19+
MEDIUM = "m",
20+
SMALL = "s",
21+
}
22+
23+
interface CardCTA {
24+
text: string;
25+
onClick: MouseEventHandler<HTMLButtonElement>;
26+
}
27+
28+
export interface CardProps extends React.PropsWithChildren {
29+
/** String of custom classes to extend the default styling of the component. */
30+
className?: string;
31+
/** Style variant to be applied to rendered component. */
32+
variants?: CardVariant[];
33+
/** Informational content to be displayed in the footer. */
34+
footerDetails?: string | ReactNode;
35+
/** An array of links to be rendered in the footer of the card. */
36+
footerLinks?: ReactElement<HTMLAnchorElement>[];
37+
/** The size of the image displayed in the top left corner. Defaults to medium */
38+
imageSize: CardImageSize;
39+
/** The image to be displayed in the top left corner */
40+
image: ReactElement<HTMLImageElement>;
41+
/** Border radius of the image. Defaults to circle */
42+
imageBorderRadius?: CardImageBorderRadius;
43+
/** The title. Can be text or a react node */
44+
title?: string | ReactNode;
45+
/** A call to action. rendered in the top right corner */
46+
cta?: CardCTA;
47+
}

src/lib/Card/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./Card";

0 commit comments

Comments
 (0)