Skip to content
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: 106 additions & 0 deletions src/components/Resource/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# ResourceGrid

## What it does

This component displays a grid of resource cards. Each card can represent either an article or a video, with an optional image, title, description, and link. Article cards show a "Read the full article" footer with an arrow.

## How to use it

1. Import the component in your Astro layout or page:

```astro
import ResourceGrid from "~/components/Resource/ResourceGrid.astro" import type {ResourceItem} from "~/components/Resource/ResourceGrid.astro"
```

2. (Optional) If you want to use imported images, import them:

```astro
import myImage from "~/assets/images/my-image.png"
```

3. Create an array of resources with the information for each resource card

4. Add the component to your page and pass in the resources:

```astro
<ResourceGrid resources={yourResourcesArray} />
```

## Example

Here's a complete example showing how to use the component:

```astro
---
import ResourceGrid from "~/components/Resource/ResourceGrid.astro"
import type { ResourceItem } from "~/components/Resource/ResourceGrid.astro"
import tokenPoolImage from "~/assets/images/token-pool.png"

const resources: ResourceItem[] = [
{
image: tokenPoolImage,
imageAlt: "Token Pool illustration",
label: "Token Pool Types",
description:
"Explore the various token pool types supported by the Cross-Chain Token (CCT) standard with Chainlink Labs.",
link: "/resources/token-pool-types",
type: "article",
},
{
label: "Getting Started with CCIP",
description:
"Learn how to build cross-chain applications using Chainlink CCIP in this comprehensive video tutorial.",
link: "https://youtube.com/watch?v=example",
type: "video",
},
{
image: "/images/cross-chain-messaging.png",
imageAlt: "Cross-chain messaging diagram",
label: "Understanding Cross-Chain Messaging",
description: "A deep dive into how cross-chain messaging works and how to implement it in your smart contracts.",
link: "/resources/cross-chain-messaging",
type: "article",
},
]
---

<ResourceGrid resources={resources} />
```

## What you need to provide

Each item in your `resources` array needs these fields:

| Field | Required? | What it is | Example |
| --------------- | --------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| **label** | Yes | The title of the resource | `"Token Pool Types"` |
| **link** | Yes | Where the card should link to (can be internal or external) | `"/resources/token-pool-types"` or `"https://youtube.com/..."` |
| **description** | Yes | A description explaining what the resource covers | `"Explore the various token pool types..."` |
| **type** | Yes | The type of resource - either `"article"` or `"video"` | `"article"` |
| **image** | No | Either an imported image or a path string | `myImage` (imported) or `"/images/token-pool.png"` (string path) |
| **imageAlt** | No | Description of the image for accessibility (required if image is provided) | `"Token Pool illustration"` |

## Where to put images

Images are optional for resource cards. You have two options:

### Option 1: Import images (recommended for images in your project)

1. Place your image file in the `src/assets/images/` directory
2. Import it at the top of your file:
```astro
import myImage from "~/assets/images/my-image.png"
```
3. Use the imported variable in your resource object

### Option 2: Use a path string (for public directory or external images)

1. Place your image file in the `/public/images/` directory
2. Reference it with the full path starting with `/images/`

Both approaches work! Use imported images for better optimization, or use path strings for simplicity.

## Resource types

- **article**: Displays "Read the full article" footer with an arrow icon
- **video**: No special footer (just the card with title and description)
47 changes: 47 additions & 0 deletions src/components/Resource/ResourceCard.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
import { Typography, SvgArrowRight2 } from "@chainlink/blocks"
import type { ImageMetadata } from "astro"
import styles from "./ResourceCard.module.css"

interface Props {
image?: string | ImageMetadata
imageAlt?: string
label: string
link: string
description: string
type: "article" | "video"
}

const { image, imageAlt, label, link, description, type } = Astro.props

const imageSrc = typeof image === "string" ? image : image?.src
---

<a href={link} class={styles.card}>
{
imageSrc && (
<div class={styles.imageWrapper}>
<img src={imageSrc} alt={imageAlt} class={styles.image} />
</div>
)
}
<div class={styles.content}>
<Typography
variant="body-semi-l"
style={{
marginBottom: "var(--space-8x)",
}}>{label}</Typography
>
<Typography variant="body-s" color="muted">
{description}
</Typography>
</div>
{
type === "article" && (
<div class={styles.cardFooter}>
<span class={styles.footerText}>Read the full article</span>
<SvgArrowRight2 color="muted" width={12} height={12} />
</div>
)
}
</a>
79 changes: 79 additions & 0 deletions src/components/Resource/ResourceCard.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
.card {
display: flex;
flex-direction: column;
background: var(--color-background);
padding: var(--space-6x);
gap: var(--space-4x);
border-right: 1px solid var(--border);
border-bottom: 1px solid var(--border);
text-decoration: none;
transition: background-color 0.2s;
min-height: 329px;
cursor: default;
}

.card:nth-child(-n + 3) {
border-top: 1px solid var(--border);
}

.card:hover {
background-color: var(--muted);
}

.imageWrapper {
width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
border-radius: var(--space-2x);
}

.image {
width: 100%;
height: 100%;
object-fit: cover;
}

.content {
display: flex;
flex-direction: column;
flex: 1;
}

.cardLabel {
font-size: 16px;
font-weight: 525;
color: var(--foreground);
margin-bottom: var(--space-2x);
}

.cardFooter {
display: flex;
align-items: center;
width: 100%;
gap: var(--space-2x);
margin-top: var(--space-4x);
}

.footerText {
font-size: 14px;
color: var(--color-text-secondary);
}
@media screen and (max-width: 1024px) {
.card:nth-child(-n + 3) {
border-top: none;
}

.card:nth-child(-n + 2) {
border-top: 1px solid var(--border);
}
}

@media screen and (max-width: 768px) {
.card:nth-child(n) {
border-top: none;
}

.card:nth-child(1) {
border-top: 1px solid var(--border);
}
}
24 changes: 24 additions & 0 deletions src/components/Resource/ResourceGrid.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
import type { ImageMetadata } from "astro"
import ResourceCard from "./ResourceCard.astro"
import styles from "./ResourceGrid.module.css"

export interface ResourceItem {
image?: string | ImageMetadata
imageAlt?: string
label: string
link: string
description: string
type: "article" | "video"
}

interface Props {
resources: ResourceItem[]
}

const { resources } = Astro.props
---

<div class={styles.grid}>
{resources.map((resource) => <ResourceCard {...resource} />)}
</div>
17 changes: 17 additions & 0 deletions src/components/Resource/ResourceGrid.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
border-left: 1px solid var(--border);
}

@media (max-width: 1024px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}

@media (max-width: 768px) {
.grid {
grid-template-columns: 1fr;
}
}
23 changes: 23 additions & 0 deletions src/components/Resource/ResourceSection.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
import { Typography } from "@chainlink/blocks"
import ResourceGrid from "./ResourceGrid.astro"
import type { ResourceItem } from "./ResourceGrid.astro"
import styles from "./ResourceSection.module.css"

interface Props {
title: string
resources: ResourceItem[]
}

const { title, resources } = Astro.props
---

<section class={styles.section}>
<Typography
variant="h2"
style={{
fontSize: "32px",
}}>{title}</Typography
>
<ResourceGrid resources={resources} />
</section>
6 changes: 6 additions & 0 deletions src/components/Resource/ResourceSection.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.section {
display: flex;
flex-direction: column;
gap: var(--space-8x);
margin: 56px 0;
}
9 changes: 8 additions & 1 deletion src/components/TabGrid/TabGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ export const TabGrid = ({ tabs, header, columns = 3 }: TabGridProps) => {
return (
<Tabs defaultValue={tabs[0].name}>
<header className={styles.gridHeader}>
<Typography variant="h2">{header}</Typography>
<Typography
variant="h2"
style={{
fontSize: "32px",
}}
>
{header}
</Typography>
<TabsList className={styles.tabsList}>
{tabs.map((tab) => (
<TabsTrigger key={tab.name} value={tab.name} className={styles.tabsTrigger}>
Expand Down
28 changes: 28 additions & 0 deletions src/layouts/DocsV3Layout/DocsV3Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import LeftSidebar from "~/components/LeftSidebar/LeftSidebar.astro"
import PageContent from "~/components/PageContent/PageContent.astro"
import { TabGrid } from "~/components/TabGrid/TabGrid"
import LayoutHero from "~/components/LayoutHero/LayoutHero.astro"
import ResourceSection from "~/components/Resource/ResourceSection.astro"
import MediaSection from "~/components/MediaSection/MediaSection.astro"
import QuickLinkCard from "~/components/QuickLinkCard/QuickLinkCard.astro"
import {
Expand Down Expand Up @@ -40,6 +41,31 @@ const currentPage = new URL(Astro.request.url).pathname

const includeLinkToWalletScript = !!Astro.props.frontmatter.metadata?.linkToWallet

// Example resources data
const exampleResources = [
{
label: "Token Pool Types",
description:
"Explore the various token pool types supported by the Cross-Chain Token (CCT) standard with Chainlink Labs. Explore the various token pool types supported by the Cross-Chain Token (CCT) standard with Chainlink Labs...",
link: "/resources/token-pool-types",
type: "article",
},
{
label: "Token Pool Types",
description:
"Explore the various token pool types supported by the Cross-Chain Token (CCT) standard with Chainlink Labs. Explore the various token pool types supported by the Cross-Chain Token (CCT) standard with Chainlink Labs...",
link: "/resources/token-pool-types",
type: "article",
},
{
label: "Token Pool Types",
description:
"Explore the various token pool types supported by the Cross-Chain Token (CCT) standard with Chainlink Labs. Explore the various token pool types supported by the Cross-Chain Token (CCT) standard with Chainlink Labs...",
link: "/resources/token-pool-types",
type: "article",
},
]

// Example tutorial data
const exampleTutorials = [
{
Expand Down Expand Up @@ -188,6 +214,8 @@ const quickLinks = [
]}
image="/images/ccip/ccip-hero.png"
/>
<TabGrid header="Tutorials" client:visible tabs={exampleTutorials} />
<ResourceSection title="Resources" resources={exampleResources} />
<QuickLinkCard links={quickLinks} />
<PageContent {titleHeading}>
<TabGrid header="Tutorials" client:visible tabs={exampleTutorials} />
Expand Down
Loading