Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
3 changes: 3 additions & 0 deletions public/assets/icons/upper-right-arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions src/components/TabGrid/GridCard.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.card {
display: flex;
background: var(--color-background);
padding: var(--space-6x);
align-items: start;
gap: var(--space-6x);

&:hover .cardFooter {
opacity: 1;
}
}

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

.cardFooter {
opacity: 0;
margin-top: auto;
/* enforcing a width */
min-width: 16px;
}

.cardFooter img {
width: 10px;
height: 10px;
}

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

.cardDescription {
color: var(--color-text-secondary);
font-size: 0.9375rem;
line-height: 1.6;
margin: 0;
}
41 changes: 41 additions & 0 deletions src/components/TabGrid/GridCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Typography } from "@chainlink/blocks"
import styles from "./GridCard.module.css"

export interface GridItem {
title: string
description: string
link: string
columns?: number
index?: number
}

export const GridCard = ({ title, description, link, columns = 3, index = 0 }: GridItem) => {
// Calculate position in grid
const row = Math.floor(index / columns)
const col = index % columns
const isFirstRow = row === 0
const isFirstColumn = col === 0

// Dynamic border styles
const borderStyle: React.CSSProperties = {
borderTop: isFirstRow ? "1px solid var(--border)" : undefined,
borderLeft: isFirstColumn ? "1px solid var(--border)" : undefined,
borderRight: "1px solid var(--border)",
borderBottom: "1px solid var(--border)",
}

return (
<a href={link} className={styles.card} style={borderStyle}>
<div>
<h3 className={styles.cardTitle}>{title}</h3>
<Typography variant="body-s" style={{ lineHeight: "24px" }}>
{description}
</Typography>
</div>

<div className={styles.cardFooter}>
<img src="/assets/icons/upper-right-arrow.svg" alt="arrow" />
</div>
</a>
)
}
17 changes: 17 additions & 0 deletions src/components/TabGrid/ItemGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { GridCard, GridItem } from "./GridCard.tsx"
import styles from "./TabGrid.module.css"

interface ItemGridProps {
links: GridItem[]
columns?: number
}

export const ItemGrid = ({ links, columns = 3 }: ItemGridProps) => {
return (
<div className={styles.grid} style={{ gridTemplateColumns: `repeat(${columns}, 1fr)` }}>
{links.map((link, index) => (
<GridCard key={`${link.title}-${index}`} {...link} columns={columns} index={index} />
))}
</div>
)
}
89 changes: 89 additions & 0 deletions src/components/TabGrid/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# TabGrid Component

A tabbed interface for displaying grid items organized by category.

## What is this?

The TabGrid component displays a collection of items in a clean, organized layout with tabs. Each tab represents a category of items (like "EVM" or "Solana"), and clicking on a tab shows the relevant items as clickable cards.

This component is useful when you have multiple items and want to group them by topic or category, making it easier for users to find what they need.

## Usage

```tsx
import { TabGrid } from "@components/TabGrid/TabGrid"
;<TabGrid
header="Tutorials"
tabs={[
{
name: "Getting Started",
links: [
{
title: "Quick Start Guide",
description: "Learn the basics in 5 minutes",
link: "/docs/quickstart",
},
{
title: "Installation",
description: "Set up your development environment",
link: "/docs/installation",
},
],
},
{
name: "Advanced",
links: [
{
title: "Architecture Overview",
description: "Understand the system design",
link: "/docs/architecture",
},
],
},
]}
/>
```

## How to set it up

The component requires a `tabs` prop, which is an array of tab objects. Each tab object contains:

- A **name** (the label shown on the tab button)
- A list of **links** (the items shown when that tab is active)

Each grid item needs three pieces of information:

- **title** - The name of the item
- **description** - A short sentence explaining what the item covers
- **link** - The URL where the item can be found

## Props Reference

### `TabGrid`

| Prop | Type | Required | Description |
| --------- | -------- | -------- | ------------------------------------------------- |
| `header` | `string` | Yes | The heading text displayed above the tabs |
| `tabs` | `Tab[]` | Yes | List of tabs, each containing a category of items |
| `columns` | `number` | No | Number of columns in the grid (defaults to 2) |

### `Tab`

| Property | Type | Required | Description |
| -------- | ------------ | -------- | -------------------------------------------------------- |
| `name` | `string` | Yes | The label displayed on the tab (e.g., "Getting Started") |
| `links` | `GridItem[]` | Yes | The list of items to show when this tab is selected |

### `GridItem`

| Property | Type | Required | Description |
| ------------- | -------- | -------- | -------------------------------------------- |
| `title` | `string` | Yes | The item's heading |
| `description` | `string` | Yes | A brief explanation of what users will learn |
| `link` | `string` | Yes | The URL path to the item page |

## Components

- **TabGrid** - Main container with tabs and header
- **ItemGrid** - Grid layout for item cards
- **GridCard** - Individual item card with hover effects
45 changes: 45 additions & 0 deletions src/components/TabGrid/TabGrid.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
.grid {
display: grid;
}

.tutorialHeader {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-8x);
}

.tabsTrigger {
height: 32px;
padding: var(--space-1x) var(--space-2x);
justify-content: center;
align-items: center;
border-radius: var(--space-2x);
background-color: var(--pill);
border: 1px solid var(--pill-border);
}

.tabsTrigger:hover {
background-color: var(--pill-hover);
}

.tabsTrigger[data-state="active"] {
background-color: var(--pill-active);
border-color: var(--pill-active);

& p {
color: var(--pill-active-foreground);
}
}

.tabsList {
display: flex;
gap: var(--space-2x);
border-bottom: 0;
}

@media (max-width: 768px) {
.grid {
grid-template-columns: 1fr;
}
}
40 changes: 40 additions & 0 deletions src/components/TabGrid/TabGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import styles from "./TabGrid.module.css"
import { GridItem } from "./GridCard.tsx"
import { ItemGrid } from "./ItemGrid.tsx"
import { Tabs, TabsContent, TabsList, TabsTrigger, Typography } from "@chainlink/blocks"

export interface Tab {
name: string
links: GridItem[]
}

interface TabGridProps {
tabs: Tab[]
header: string
columns?: number
}

export const TabGrid = ({ tabs, header, columns }: TabGridProps) => {
return (
<Tabs defaultValue={tabs[0].name}>
<header className={styles.tutorialHeader}>
<Typography variant="h2">{header}</Typography>
<TabsList className={styles.tabsList}>
{tabs.map((tab) => (
<TabsTrigger key={tab.name} value={tab.name} className={styles.tabsTrigger}>
<Typography variant="body-s">{tab.name}</Typography>
</TabsTrigger>
))}
</TabsList>
</header>

{tabs.map((tab) => (
<TabsContent key={tab.name} value={tab.name}>
<div className={styles.tutorialSection}>
<ItemGrid links={tab.links} columns={columns} />
</div>
</TabsContent>
))}
</Tabs>
)
}
96 changes: 96 additions & 0 deletions src/layouts/DocsV3Layout/DocsV3Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { BaseFrontmatter } from "~/content.config"
import * as CONFIG from "~/config"
import LeftSidebar from "~/components/LeftSidebar/LeftSidebar.astro"
import PageContent from "~/components/PageContent/PageContent.astro"
import { TabGrid } from "~/components/TabGrid/TabGrid"

interface Props {
frontmatter: BaseFrontmatter
Expand All @@ -27,6 +28,100 @@ const formattedContentTitle = `${frontmatter.title} | ${CONFIG.SITE.title}`
const currentPage = new URL(Astro.request.url).pathname

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

// Example tutorial data
const exampleTutorials = [
{
name: "EVM",
links: [
{
title: "Acquire Test Tokens",
description: "Get test tokens in minutes; build and test cross-chain apps with zero friction.",
link: "/tutorials/acquire-test-tokens",
},
{
title: "Transfer Tokens",
description: "Unlock seamless token transfers from contracts; learn, code, and deploy.",
link: "/tutorials/transfer-tokens",
},
{
title: "Transfer Tokens with Data",
description: "Go beyond basic transfers with logic-infused token movements in your EVM contracts.",
link: "/tutorials/transfer-tokens-data",
},
{
title: "Using the Token Manager",
description: "Effortlessly manage CCTs by tracking, importing and organizing tokens from your dashboard.",
link: "/tutorials/token-manager",
},
{
title: "Using the JS SDK",
description: "Integrate CCIP in your frontend or backend effortlessly with JavaScript SDK.",
link: "/tutorials/js-sdk",
},
{
title: "Check Message Status",
description: "Retrieve real-time status of your offchain transaction from EVM.",
link: "/tutorials/check-message-status",
},
{
title: "Transfer Tokens Between EOAs",
description: "Send tokens offchain from an Externally Owned Account with clear steps.",
link: "/tutorials/transfer-tokens-eoa",
},
{
title: "Using the CLI",
description: "Use offchain tools from CCIP to simplify your Ethereum workflows.",
link: "/tutorials/cli",
},
{
title: "Deploy and Register a CCT",
description: "Use RemixIDE to launch and configure tokens for cross-chain transfers on CCIP.",
link: "/tutorials/deploy-register-cct",
},
{
title: "Register CCT Burn & Mint EOA",
description: "Implement burn-mint cross-chain token logic with CCIP using Hardhat or Foundry.",
link: "/tutorials/register-cct-burn-mint",
},
{
title: "Register CCT Lock & Mint EOA",
description: "Implement a lock-mint token registration workflow with CCIP and Hardhat or Foundry.",
link: "/tutorials/register-cct-lock-mint",
},
{
title: "Set Token Pool Rate Limits",
description: "Update rate limiter settings for your cross-chain tokens using Hardhat or Foundry.",
link: "/tutorials/token-pool-rate-limits",
},
],
},
{
name: "Solana",
links: [
{
title: "Getting Started with Solana",
description: "Learn the basics of building on Solana blockchain.",
link: "/tutorials/solana-getting-started",
},
{
title: "Solana Token Transfers",
description: "Transfer tokens on the Solana blockchain.",
link: "/tutorials/solana-transfers",
},
],
},
{
name: "Aptos",
links: [
{
title: "Getting Started with Aptos",
description: "Start building on the Aptos blockchain.",
link: "/tutorials/aptos-getting-started",
},
],
},
]
---

<BaseLayout title={formattedContentTitle} metadata={frontmatter.metadata} pageTitle={frontmatter.title}>
Expand All @@ -40,6 +135,7 @@ const includeLinkToWalletScript = !!Astro.props.frontmatter.metadata?.linkToWall
</aside>
<div id="grid-main">
<PageContent {titleHeading}>
<TabGrid header="Tutorials" client:visible tabs={exampleTutorials} />
<slot />
</PageContent>
</div>
Expand Down
Loading
Loading