Skip to content

Commit 7bd02b4

Browse files
Merge pull request #8 from code4rena-dev/develop
Release V 1.0.0 with new Card component
2 parents 975c7b9 + fa0d73c commit 7bd02b4

File tree

8 files changed

+327
-1
lines changed

8 files changed

+327
-1
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@code4rena/components-library",
3-
"version": "0.1.0",
3+
"version": "1.0.0",
44
"description": "Code4rena's official components library ",
55
"main": "dist/components-library.umd.js",
66
"module": "dist/components-library.mjs",

public/images/default-avatar.png

31.3 KB
Loading

src/lib/Card/Card.scss

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

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>Team lead</div>,
33+
title: "MangoBurger",
34+
imageSize: CardImageSize.MEDIUM,
35+
image: <img src="/images/default-avatar.png" />,
36+
imageBorderRadius: CardImageBorderRadius.CIRCLE,
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: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
/**
9+
* A stylized Code4rena card with 4 variants and optional footer, cta button, and title.
10+
* Requires an image tag for the top left corner and children for the body.
11+
*
12+
* __Available variants:__
13+
* - `TRANSPARENT` - makes the background of the card transparent
14+
* - `OUTLINED` - adds border around the card
15+
* - `COMPACT` - displays the body below the image to allow the card to be more narrow
16+
* - `BOTTOM_ALIGNED` - makes the title and body bottom aligned instead of top aligned
17+
*
18+
* @param children - Elements to be rendered inside the body of the card, passed in as children.
19+
* @param className - String of custom classes to extend the default styling of the component.
20+
* @param variants - Array of style variants to be applied to the rendered component.
21+
* @param footerDetails - Informational content to be rendered on the left side of the footer.
22+
* @param footerLinks - Array of anchor elements to be rendered in the right side of the footer.
23+
* @param imageSize - The size of the image displayed in the top corner. Can be small, medium or large. Defaults to medium
24+
* @param image - Image element to be rendered in the top left corner
25+
* @param imageBorderRadius - Border radius of the image. Can be circle, small, medium, or large. Defaults to circle.
26+
* @param title - The title to be rendered at the top of the card body. Can be string or react node.
27+
* @param cta - A call to action button rendered in the top right corner. Includes text and an onClick handler.
28+
*/
29+
export const Card: React.FC<CardProps> = ({
30+
children,
31+
className,
32+
variants,
33+
footerDetails,
34+
footerLinks,
35+
imageSize = CardImageSize.MEDIUM,
36+
image,
37+
imageBorderRadius = CardImageBorderRadius.CIRCLE,
38+
title,
39+
cta,
40+
}) => {
41+
return (
42+
<div
43+
className={clsx(
44+
"card",
45+
className && className,
46+
variants && variants.map((variant) => `card--${variant}`)
47+
)}
48+
>
49+
{/* Main section of card */}
50+
<div className="card__main">
51+
<div
52+
className={`card__image card__image--${imageSize} card__image--radius-${imageBorderRadius}`}
53+
>
54+
{image}
55+
</div>
56+
{/* body */}
57+
<div className="card__body">
58+
{title && <h2 className="card__title">{title}</h2>}
59+
{children}
60+
</div>
61+
{/* CTA */}
62+
{cta && (
63+
<Button
64+
className="card__cta"
65+
onClick={cta.onClick}
66+
label={cta.text}
67+
variant={ButtonVariant.SECONDARY}
68+
type={ButtonType.BUTTON}
69+
size={ButtonSize.NARROW}
70+
/>
71+
)}
72+
</div>
73+
{/* Footer */}
74+
{(footerDetails || footerLinks) && (
75+
<footer className="card__footer">
76+
{footerDetails && (
77+
<p className="card__footer-details">{footerDetails}</p>
78+
)}
79+
{footerLinks && (
80+
<div className="card__footer-link-wrapper">
81+
{footerLinks.map((link) => link)}
82+
</div>
83+
)}
84+
</footer>
85+
)}
86+
</div>
87+
);
88+
};

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";

src/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export * from "./EyebrowBar";
77
export * from "./Input";
88
export * from "./NavBar";
99
export * from "./Tag";
10+
export * from "./Card";

0 commit comments

Comments
 (0)