From dc3b24dd4868adb9d8d5861ed5fba1ef3598638e Mon Sep 17 00:00:00 2001 From: Tyrel Chambers Date: Thu, 16 Oct 2025 12:40:54 -0400 Subject: [PATCH 1/9] add tutorial foundation --- .../TutorialSection/TutorialCard.module.css | 66 +++++++++++ .../TutorialSection/TutorialCard.tsx | 29 +++++ .../TutorialSection/TutorialGrid.tsx | 16 +++ .../TutorialSection.module.css | 75 ++++++++++++ .../TutorialSection/TutorialSection.tsx | 40 +++++++ src/layouts/DocsV3Layout/DocsV3Layout.astro | 111 ++++++++++++++++++ 6 files changed, 337 insertions(+) create mode 100644 src/components/TutorialSection/TutorialCard.module.css create mode 100644 src/components/TutorialSection/TutorialCard.tsx create mode 100644 src/components/TutorialSection/TutorialGrid.tsx create mode 100644 src/components/TutorialSection/TutorialSection.module.css create mode 100644 src/components/TutorialSection/TutorialSection.tsx diff --git a/src/components/TutorialSection/TutorialCard.module.css b/src/components/TutorialSection/TutorialCard.module.css new file mode 100644 index 00000000000..245db16afd9 --- /dev/null +++ b/src/components/TutorialSection/TutorialCard.module.css @@ -0,0 +1,66 @@ +.card { + display: flex; + flex-direction: column; + background: var(--color-background); + border: 1px solid var(--color-border); + border-radius: 12px; + padding: 1.5rem; + text-decoration: none; + color: inherit; + transition: all 0.2s ease; + cursor: pointer; +} + +.card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + border-color: var(--color-accent); +} + +.cardHeader { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 1rem; + margin-bottom: 0.75rem; +} + +.cardTitle { + font-size: 1.125rem; + font-weight: 600; + color: var(--color-text-primary); + margin: 0; + line-height: 1.4; +} + +.typeBadge { + background: var(--color-accent); + color: white; + padding: 0.25rem 0.75rem; + border-radius: 6px; + font-size: 0.75rem; + font-weight: 500; + white-space: nowrap; + flex-shrink: 0; +} + +.cardDescription { + color: var(--color-text-secondary); + font-size: 0.9375rem; + line-height: 1.6; + margin: 0; +} + +@media (max-width: 768px) { + .card { + padding: 1.25rem; + } + + .cardTitle { + font-size: 1rem; + } + + .cardDescription { + font-size: 0.875rem; + } +} diff --git a/src/components/TutorialSection/TutorialCard.tsx b/src/components/TutorialSection/TutorialCard.tsx new file mode 100644 index 00000000000..6a5d387fa76 --- /dev/null +++ b/src/components/TutorialSection/TutorialCard.tsx @@ -0,0 +1,29 @@ +import styles from "./TutorialCard.module.css" + +export interface TutorialLink { + title: string + description: string + link: string + type: string +} + +interface TutorialCardProps extends TutorialLink {} + +export const TutorialCard = ({ title, description, link, type }: TutorialCardProps) => { + const isExternal = link.startsWith("http") + + return ( + +
+

{title}

+ {type} +
+

{description}

+
+ ) +} diff --git a/src/components/TutorialSection/TutorialGrid.tsx b/src/components/TutorialSection/TutorialGrid.tsx new file mode 100644 index 00000000000..575b9465337 --- /dev/null +++ b/src/components/TutorialSection/TutorialGrid.tsx @@ -0,0 +1,16 @@ +import styles from "./TutorialSection.module.css" +import { TutorialCard, TutorialLink } from "./TutorialCard" + +interface TutorialGridProps { + links: TutorialLink[] +} + +export const TutorialGrid = ({ links }: TutorialGridProps) => { + return ( +
+ {links.map((link, index) => ( + + ))} +
+ ) +} diff --git a/src/components/TutorialSection/TutorialSection.module.css b/src/components/TutorialSection/TutorialSection.module.css new file mode 100644 index 00000000000..04f491eff28 --- /dev/null +++ b/src/components/TutorialSection/TutorialSection.module.css @@ -0,0 +1,75 @@ +.tutorialSection { + width: 100%; + margin: 2rem 0; +} + +.tabContainer { + display: flex; + gap: 0.5rem; + margin-bottom: 2rem; + border-bottom: 1px solid var(--color-border); + flex-wrap: wrap; +} + +.tabButton { + background: transparent; + border: none; + padding: 0.75rem 1.5rem; + cursor: pointer; + font-size: 1rem; + font-weight: 500; + color: var(--color-text-secondary); + transition: all 0.2s ease; + border-bottom: 2px solid transparent; + position: relative; + bottom: -1px; +} + +.tabButton:hover { + color: var(--color-text-primary); +} + +.tabButton.activeTab { + color: var(--color-accent); + border-bottom-color: var(--color-accent); +} + +.contentContainer { + margin-top: 1.5rem; +} + +.description { + margin-bottom: 1.5rem; + color: var(--color-text-secondary); + font-size: 1rem; + line-height: 1.6; +} + +.grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1.5rem; +} + +.emptyState { + color: var(--color-text-secondary); + text-align: center; + padding: 2rem; + font-style: italic; +} + +.tutorialHeader { + display: flex; + justify-content: space-between; + align-items: center; +} +@media (max-width: 768px) { + .grid { + grid-template-columns: 1fr; + } + + .tabButton { + font-size: 0.9rem; + padding: 0.5rem 1rem; + } +} diff --git a/src/components/TutorialSection/TutorialSection.tsx b/src/components/TutorialSection/TutorialSection.tsx new file mode 100644 index 00000000000..00b93f15212 --- /dev/null +++ b/src/components/TutorialSection/TutorialSection.tsx @@ -0,0 +1,40 @@ +import styles from "./TutorialSection.module.css" +import { TutorialLink } from "./TutorialCard.tsx" +import { TutorialGrid } from "./TutorialGrid.tsx" +import { Tabs, TabsContent, TabsList, TabsTrigger, Typography } from "@chainlink/blocks" + +export interface Tab { + name: string + description?: string + links: TutorialLink[] +} + +interface TutorialSectionProps { + tabs: Tab[] + defaultTab?: string +} + +export const TutorialSection = ({ tabs }: TutorialSectionProps) => { + return ( + +
+ Tutorials + + {tabs.map((tab) => ( + + {tab.name} + + ))} + +
+ + {tabs.map((tab) => ( + +
+ +
+
+ ))} +
+ ) +} diff --git a/src/layouts/DocsV3Layout/DocsV3Layout.astro b/src/layouts/DocsV3Layout/DocsV3Layout.astro index 2f3e272954a..6dbab8615cb 100644 --- a/src/layouts/DocsV3Layout/DocsV3Layout.astro +++ b/src/layouts/DocsV3Layout/DocsV3Layout.astro @@ -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 { TutorialSection } from "~/components/TutorialSection/TutorialSection" interface Props { frontmatter: BaseFrontmatter @@ -27,6 +28,115 @@ 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", + type: "EVM", + }, + { + title: "Transfer Tokens", + description: "Unlock seamless token transfers from contracts; learn, code, and deploy.", + link: "/tutorials/transfer-tokens", + type: "EVM", + }, + { + title: "Transfer Tokens with Data", + description: "Go beyond basic transfers with logic-infused token movements in your EVM contracts.", + link: "/tutorials/transfer-tokens-data", + type: "EVM", + }, + { + title: "Using the Token Manager", + description: "Effortlessly manage CCTs by tracking, importing and organizing tokens from your dashboard.", + link: "/tutorials/token-manager", + type: "EVM", + }, + { + title: "Using the JS SDK", + description: "Integrate CCIP in your frontend or backend effortlessly with JavaScript SDK.", + link: "/tutorials/js-sdk", + type: "EVM", + }, + { + title: "Check Message Status", + description: "Retrieve real-time status of your offchain transaction from EVM.", + link: "/tutorials/check-message-status", + type: "EVM", + }, + { + title: "Transfer Tokens Between EOAs", + description: "Send tokens offchain from an Externally Owned Account with clear steps.", + link: "/tutorials/transfer-tokens-eoa", + type: "EVM", + }, + { + title: "Using the CLI", + description: "Use offchain tools from CCIP to simplify your Ethereum workflows.", + link: "/tutorials/cli", + type: "EVM", + }, + { + 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", + type: "EVM", + }, + { + 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", + type: "EVM", + }, + { + 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", + type: "EVM", + }, + { + 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", + type: "EVM", + }, + ], + }, + { + name: "Solana", + links: [ + { + title: "Getting Started with Solana", + description: "Learn the basics of building on Solana blockchain.", + link: "/tutorials/solana-getting-started", + type: "Solana", + }, + { + title: "Solana Token Transfers", + description: "Transfer tokens on the Solana blockchain.", + link: "/tutorials/solana-transfers", + type: "Solana", + }, + ], + }, + { + name: "Aptos", + links: [ + { + title: "Getting Started with Aptos", + description: "Start building on the Aptos blockchain.", + link: "/tutorials/aptos-getting-started", + type: "Aptos", + }, + ], + }, +] --- @@ -40,6 +150,7 @@ const includeLinkToWalletScript = !!Astro.props.frontmatter.metadata?.linkToWall
+
From 247a27ce9b12ac2e904253c68ec172916ab1753d Mon Sep 17 00:00:00 2001 From: Tyrel Chambers Date: Thu, 16 Oct 2025 14:00:13 -0400 Subject: [PATCH 2/9] Add tutorial components --- .../TutorialSection/TutorialCard.module.css | 67 ++++++++++--------- .../TutorialSection/TutorialCard.tsx | 26 +++---- .../TutorialSection/TutorialGrid.tsx | 2 +- .../TutorialSection.module.css | 40 ++++++++--- .../TutorialSection/TutorialSection.tsx | 6 +- src/layouts/DocsV3Layout/DocsV3Layout.astro | 2 +- 6 files changed, 82 insertions(+), 61 deletions(-) diff --git a/src/components/TutorialSection/TutorialCard.module.css b/src/components/TutorialSection/TutorialCard.module.css index 245db16afd9..1dc91f95bfe 100644 --- a/src/components/TutorialSection/TutorialCard.module.css +++ b/src/components/TutorialSection/TutorialCard.module.css @@ -1,47 +1,50 @@ .card { display: flex; - flex-direction: column; background: var(--color-background); - border: 1px solid var(--color-border); - border-radius: 12px; - padding: 1.5rem; - text-decoration: none; - color: inherit; - transition: all 0.2s ease; - cursor: pointer; + padding: var(--space-6x); + align-items: start; + gap: var(--space-6x); + + &:hover .cardFooter { + opacity: 1; + } +} + +.card:first-child { + border: 1px solid var(--border); +} + +.card:nth-child(-n + 3) { + border-top: 1px solid var(--border); +} + +.card:not(:first-child) { + border-bottom: 1px solid var(--border); + border-right: 1px solid var(--border); +} +.card:nth-child(3n + 1) { + border-left: 1px solid var(--border); } .card:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); - border-color: var(--color-accent); + background-color: var(--muted); } -.cardHeader { +.cardFooter { + opacity: 0; + /* enforcing a width */ + min-width: 16px; + max-width: 16px; display: flex; - justify-content: space-between; - align-items: flex-start; - gap: 1rem; - margin-bottom: 0.75rem; + align-items: end; + height: 100%; } .cardTitle { - font-size: 1.125rem; - font-weight: 600; - color: var(--color-text-primary); - margin: 0; - line-height: 1.4; -} - -.typeBadge { - background: var(--color-accent); - color: white; - padding: 0.25rem 0.75rem; - border-radius: 6px; - font-size: 0.75rem; - font-weight: 500; - white-space: nowrap; - flex-shrink: 0; + font-size: 16px; + font-weight: 525; + color: var(--foreground); + margin-bottom: var(--space-2x); } .cardDescription { diff --git a/src/components/TutorialSection/TutorialCard.tsx b/src/components/TutorialSection/TutorialCard.tsx index 6a5d387fa76..c16dc5228e5 100644 --- a/src/components/TutorialSection/TutorialCard.tsx +++ b/src/components/TutorialSection/TutorialCard.tsx @@ -1,29 +1,25 @@ +import { Typography, SvgArrowDiagonalUpperRight } from "@chainlink/blocks" import styles from "./TutorialCard.module.css" export interface TutorialLink { title: string description: string link: string - type: string } -interface TutorialCardProps extends TutorialLink {} - -export const TutorialCard = ({ title, description, link, type }: TutorialCardProps) => { - const isExternal = link.startsWith("http") - +export const TutorialCard = ({ title, description, link }: TutorialLink) => { return ( - -
+ +

{title}

- {type} + + {description} + +
+ +
+
-

{description}

) } diff --git a/src/components/TutorialSection/TutorialGrid.tsx b/src/components/TutorialSection/TutorialGrid.tsx index 575b9465337..d1c92c322e5 100644 --- a/src/components/TutorialSection/TutorialGrid.tsx +++ b/src/components/TutorialSection/TutorialGrid.tsx @@ -1,5 +1,5 @@ +import { TutorialCard, TutorialLink } from "./TutorialCard.tsx" import styles from "./TutorialSection.module.css" -import { TutorialCard, TutorialLink } from "./TutorialCard" interface TutorialGridProps { links: TutorialLink[] diff --git a/src/components/TutorialSection/TutorialSection.module.css b/src/components/TutorialSection/TutorialSection.module.css index 04f491eff28..13a67ad8d73 100644 --- a/src/components/TutorialSection/TutorialSection.module.css +++ b/src/components/TutorialSection/TutorialSection.module.css @@ -47,22 +47,44 @@ .grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 1.5rem; -} - -.emptyState { - color: var(--color-text-secondary); - text-align: center; - padding: 2rem; - font-style: italic; + grid-template-columns: 1fr 1fr 1fr; } .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: 0.5rem; +} + @media (max-width: 768px) { .grid { grid-template-columns: 1fr; diff --git a/src/components/TutorialSection/TutorialSection.tsx b/src/components/TutorialSection/TutorialSection.tsx index 00b93f15212..a99b31a02e5 100644 --- a/src/components/TutorialSection/TutorialSection.tsx +++ b/src/components/TutorialSection/TutorialSection.tsx @@ -19,10 +19,10 @@ export const TutorialSection = ({ tabs }: TutorialSectionProps) => {
Tutorials - + {tabs.map((tab) => ( - - {tab.name} + + {tab.name} ))} diff --git a/src/layouts/DocsV3Layout/DocsV3Layout.astro b/src/layouts/DocsV3Layout/DocsV3Layout.astro index 6dbab8615cb..c6a06712c4f 100644 --- a/src/layouts/DocsV3Layout/DocsV3Layout.astro +++ b/src/layouts/DocsV3Layout/DocsV3Layout.astro @@ -150,7 +150,7 @@ const exampleTutorials = [
- +
From 8d3a66ed059b9a3cd811a692deed4b0b27c47be8 Mon Sep 17 00:00:00 2001 From: Tyrel Chambers Date: Thu, 16 Oct 2025 14:14:35 -0400 Subject: [PATCH 3/9] add README --- src/components/TutorialSection/README.md | 87 +++++++++++++++++++ .../TutorialSection/TutorialSection.tsx | 2 - 2 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 src/components/TutorialSection/README.md diff --git a/src/components/TutorialSection/README.md b/src/components/TutorialSection/README.md new file mode 100644 index 00000000000..0e26041b20f --- /dev/null +++ b/src/components/TutorialSection/README.md @@ -0,0 +1,87 @@ +# TutorialSection Component + +A tabbed interface for displaying tutorial links organized by category. + +## What is this? + +The TutorialSection component displays a collection of tutorials in a clean, organized layout with tabs. Each tab represents a category of tutorials (like "Getting Started" or "Advanced"), and clicking on a tab shows the relevant tutorials as clickable cards. + +This component is useful when you have multiple tutorials and want to group them by topic or difficulty level, making it easier for users to find what they need. + +## Usage + +```tsx +import { TutorialSection } from "@components/TutorialSection/TutorialSection" + +; +``` + +## 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 tutorials shown when that tab is active) + +Each tutorial link needs three pieces of information: + +- **title** - The name of the tutorial +- **description** - A short sentence explaining what the tutorial covers +- **link** - The URL where the tutorial can be found + +## Props Reference + +### `TutorialSection` + +| Prop | Type | Required | Description | +| ------ | ------- | -------- | ----------------------------------------------------- | +| `tabs` | `Tab[]` | Yes | List of tabs, each containing a category of tutorials | + +### `Tab` + +| Property | Type | Required | Description | +| -------- | ---------------- | -------- | -------------------------------------------------------- | +| `name` | `string` | Yes | The label displayed on the tab (e.g., "Getting Started") | +| `links` | `TutorialLink[]` | Yes | The list of tutorials to show when this tab is selected | + +### `TutorialLink` + +| Property | Type | Required | Description | +| ------------- | -------- | -------- | -------------------------------------------- | +| `title` | `string` | Yes | The tutorial's heading | +| `description` | `string` | Yes | A brief explanation of what users will learn | +| `link` | `string` | Yes | The URL path to the tutorial page | + +## Components + +- **TutorialSection** - Main container with tabs and header +- **TutorialGrid** - Grid layout for tutorial cards +- **TutorialCard** - Individual tutorial card with hover effects diff --git a/src/components/TutorialSection/TutorialSection.tsx b/src/components/TutorialSection/TutorialSection.tsx index a99b31a02e5..d539fb72531 100644 --- a/src/components/TutorialSection/TutorialSection.tsx +++ b/src/components/TutorialSection/TutorialSection.tsx @@ -5,13 +5,11 @@ import { Tabs, TabsContent, TabsList, TabsTrigger, Typography } from "@chainlink export interface Tab { name: string - description?: string links: TutorialLink[] } interface TutorialSectionProps { tabs: Tab[] - defaultTab?: string } export const TutorialSection = ({ tabs }: TutorialSectionProps) => { From df858354724ba9daf753797e02cdef67cbc74512 Mon Sep 17 00:00:00 2001 From: Tyrel Chambers Date: Fri, 17 Oct 2025 13:25:34 -0400 Subject: [PATCH 4/9] support N number of columns and add arrow icon --- public/assets/icons/upper-right-arrow.svg | 3 + .../GridCard.module.css} | 26 ++--- src/components/TabGrid/GridCard.tsx | 41 ++++++++ src/components/TabGrid/ItemGrid.tsx | 17 ++++ src/components/TabGrid/README.md | 89 +++++++++++++++++ src/components/TabGrid/TabGrid.module.css | 45 +++++++++ .../TabGrid.tsx} | 18 ++-- src/components/TutorialSection/README.md | 87 ----------------- .../TutorialSection/TutorialCard.tsx | 25 ----- .../TutorialSection/TutorialGrid.tsx | 16 --- .../TutorialSection.module.css | 97 ------------------- src/layouts/DocsV3Layout/DocsV3Layout.astro | 19 +--- 12 files changed, 213 insertions(+), 270 deletions(-) create mode 100644 public/assets/icons/upper-right-arrow.svg rename src/components/{TutorialSection/TutorialCard.module.css => TabGrid/GridCard.module.css} (65%) create mode 100644 src/components/TabGrid/GridCard.tsx create mode 100644 src/components/TabGrid/ItemGrid.tsx create mode 100644 src/components/TabGrid/README.md create mode 100644 src/components/TabGrid/TabGrid.module.css rename src/components/{TutorialSection/TutorialSection.tsx => TabGrid/TabGrid.tsx} (65%) delete mode 100644 src/components/TutorialSection/README.md delete mode 100644 src/components/TutorialSection/TutorialCard.tsx delete mode 100644 src/components/TutorialSection/TutorialGrid.tsx delete mode 100644 src/components/TutorialSection/TutorialSection.module.css diff --git a/public/assets/icons/upper-right-arrow.svg b/public/assets/icons/upper-right-arrow.svg new file mode 100644 index 00000000000..7f588a0dcbd --- /dev/null +++ b/public/assets/icons/upper-right-arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/TutorialSection/TutorialCard.module.css b/src/components/TabGrid/GridCard.module.css similarity index 65% rename from src/components/TutorialSection/TutorialCard.module.css rename to src/components/TabGrid/GridCard.module.css index 1dc91f95bfe..ab174828480 100644 --- a/src/components/TutorialSection/TutorialCard.module.css +++ b/src/components/TabGrid/GridCard.module.css @@ -10,22 +10,6 @@ } } -.card:first-child { - border: 1px solid var(--border); -} - -.card:nth-child(-n + 3) { - border-top: 1px solid var(--border); -} - -.card:not(:first-child) { - border-bottom: 1px solid var(--border); - border-right: 1px solid var(--border); -} -.card:nth-child(3n + 1) { - border-left: 1px solid var(--border); -} - .card:hover { background-color: var(--muted); } @@ -33,11 +17,13 @@ .cardFooter { opacity: 0; /* enforcing a width */ + margin-top: auto; min-width: 16px; - max-width: 16px; - display: flex; - align-items: end; - height: 100%; +} + +.cardFooter img { + width: 10px; + height: 10px; } .cardTitle { diff --git a/src/components/TabGrid/GridCard.tsx b/src/components/TabGrid/GridCard.tsx new file mode 100644 index 00000000000..b50910450dd --- /dev/null +++ b/src/components/TabGrid/GridCard.tsx @@ -0,0 +1,41 @@ +import { Typography, SvgArrowDiagonalUpperRight } 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 ( + +
+

{title}

+ + {description} + +
+ +
+ arrow +
+
+ ) +} diff --git a/src/components/TabGrid/ItemGrid.tsx b/src/components/TabGrid/ItemGrid.tsx new file mode 100644 index 00000000000..0a1fd2966ba --- /dev/null +++ b/src/components/TabGrid/ItemGrid.tsx @@ -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 ( +
+ {links.map((link, index) => ( + + ))} +
+ ) +} diff --git a/src/components/TabGrid/README.md b/src/components/TabGrid/README.md new file mode 100644 index 00000000000..0edc1934c0c --- /dev/null +++ b/src/components/TabGrid/README.md @@ -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" +; +``` + +## 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 diff --git a/src/components/TabGrid/TabGrid.module.css b/src/components/TabGrid/TabGrid.module.css new file mode 100644 index 00000000000..815357b55c5 --- /dev/null +++ b/src/components/TabGrid/TabGrid.module.css @@ -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; + } +} diff --git a/src/components/TutorialSection/TutorialSection.tsx b/src/components/TabGrid/TabGrid.tsx similarity index 65% rename from src/components/TutorialSection/TutorialSection.tsx rename to src/components/TabGrid/TabGrid.tsx index d539fb72531..6934972b85f 100644 --- a/src/components/TutorialSection/TutorialSection.tsx +++ b/src/components/TabGrid/TabGrid.tsx @@ -1,22 +1,24 @@ -import styles from "./TutorialSection.module.css" -import { TutorialLink } from "./TutorialCard.tsx" -import { TutorialGrid } from "./TutorialGrid.tsx" +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: TutorialLink[] + links: GridItem[] } -interface TutorialSectionProps { +interface TabGridProps { tabs: Tab[] + header: string + columns?: number } -export const TutorialSection = ({ tabs }: TutorialSectionProps) => { +export const TabGrid = ({ tabs, header, columns }: TabGridProps) => { return (
- Tutorials + {header} {tabs.map((tab) => ( @@ -29,7 +31,7 @@ export const TutorialSection = ({ tabs }: TutorialSectionProps) => { {tabs.map((tab) => (
- +
))} diff --git a/src/components/TutorialSection/README.md b/src/components/TutorialSection/README.md deleted file mode 100644 index 0e26041b20f..00000000000 --- a/src/components/TutorialSection/README.md +++ /dev/null @@ -1,87 +0,0 @@ -# TutorialSection Component - -A tabbed interface for displaying tutorial links organized by category. - -## What is this? - -The TutorialSection component displays a collection of tutorials in a clean, organized layout with tabs. Each tab represents a category of tutorials (like "Getting Started" or "Advanced"), and clicking on a tab shows the relevant tutorials as clickable cards. - -This component is useful when you have multiple tutorials and want to group them by topic or difficulty level, making it easier for users to find what they need. - -## Usage - -```tsx -import { TutorialSection } from "@components/TutorialSection/TutorialSection" - -; -``` - -## 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 tutorials shown when that tab is active) - -Each tutorial link needs three pieces of information: - -- **title** - The name of the tutorial -- **description** - A short sentence explaining what the tutorial covers -- **link** - The URL where the tutorial can be found - -## Props Reference - -### `TutorialSection` - -| Prop | Type | Required | Description | -| ------ | ------- | -------- | ----------------------------------------------------- | -| `tabs` | `Tab[]` | Yes | List of tabs, each containing a category of tutorials | - -### `Tab` - -| Property | Type | Required | Description | -| -------- | ---------------- | -------- | -------------------------------------------------------- | -| `name` | `string` | Yes | The label displayed on the tab (e.g., "Getting Started") | -| `links` | `TutorialLink[]` | Yes | The list of tutorials to show when this tab is selected | - -### `TutorialLink` - -| Property | Type | Required | Description | -| ------------- | -------- | -------- | -------------------------------------------- | -| `title` | `string` | Yes | The tutorial's heading | -| `description` | `string` | Yes | A brief explanation of what users will learn | -| `link` | `string` | Yes | The URL path to the tutorial page | - -## Components - -- **TutorialSection** - Main container with tabs and header -- **TutorialGrid** - Grid layout for tutorial cards -- **TutorialCard** - Individual tutorial card with hover effects diff --git a/src/components/TutorialSection/TutorialCard.tsx b/src/components/TutorialSection/TutorialCard.tsx deleted file mode 100644 index c16dc5228e5..00000000000 --- a/src/components/TutorialSection/TutorialCard.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Typography, SvgArrowDiagonalUpperRight } from "@chainlink/blocks" -import styles from "./TutorialCard.module.css" - -export interface TutorialLink { - title: string - description: string - link: string -} - -export const TutorialCard = ({ title, description, link }: TutorialLink) => { - return ( - -
-

{title}

- - {description} - -
- -
- -
-
- ) -} diff --git a/src/components/TutorialSection/TutorialGrid.tsx b/src/components/TutorialSection/TutorialGrid.tsx deleted file mode 100644 index d1c92c322e5..00000000000 --- a/src/components/TutorialSection/TutorialGrid.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { TutorialCard, TutorialLink } from "./TutorialCard.tsx" -import styles from "./TutorialSection.module.css" - -interface TutorialGridProps { - links: TutorialLink[] -} - -export const TutorialGrid = ({ links }: TutorialGridProps) => { - return ( -
- {links.map((link, index) => ( - - ))} -
- ) -} diff --git a/src/components/TutorialSection/TutorialSection.module.css b/src/components/TutorialSection/TutorialSection.module.css deleted file mode 100644 index 13a67ad8d73..00000000000 --- a/src/components/TutorialSection/TutorialSection.module.css +++ /dev/null @@ -1,97 +0,0 @@ -.tutorialSection { - width: 100%; - margin: 2rem 0; -} - -.tabContainer { - display: flex; - gap: 0.5rem; - margin-bottom: 2rem; - border-bottom: 1px solid var(--color-border); - flex-wrap: wrap; -} - -.tabButton { - background: transparent; - border: none; - padding: 0.75rem 1.5rem; - cursor: pointer; - font-size: 1rem; - font-weight: 500; - color: var(--color-text-secondary); - transition: all 0.2s ease; - border-bottom: 2px solid transparent; - position: relative; - bottom: -1px; -} - -.tabButton:hover { - color: var(--color-text-primary); -} - -.tabButton.activeTab { - color: var(--color-accent); - border-bottom-color: var(--color-accent); -} - -.contentContainer { - margin-top: 1.5rem; -} - -.description { - margin-bottom: 1.5rem; - color: var(--color-text-secondary); - font-size: 1rem; - line-height: 1.6; -} - -.grid { - display: grid; - grid-template-columns: 1fr 1fr 1fr; -} - -.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: 0.5rem; -} - -@media (max-width: 768px) { - .grid { - grid-template-columns: 1fr; - } - - .tabButton { - font-size: 0.9rem; - padding: 0.5rem 1rem; - } -} diff --git a/src/layouts/DocsV3Layout/DocsV3Layout.astro b/src/layouts/DocsV3Layout/DocsV3Layout.astro index c6a06712c4f..8169548be7a 100644 --- a/src/layouts/DocsV3Layout/DocsV3Layout.astro +++ b/src/layouts/DocsV3Layout/DocsV3Layout.astro @@ -6,7 +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 { TutorialSection } from "~/components/TutorialSection/TutorialSection" +import { TabGrid } from "~/components/TabGrid/TabGrid" interface Props { frontmatter: BaseFrontmatter @@ -38,73 +38,61 @@ const exampleTutorials = [ title: "Acquire Test Tokens", description: "Get test tokens in minutes; build and test cross-chain apps with zero friction.", link: "/tutorials/acquire-test-tokens", - type: "EVM", }, { title: "Transfer Tokens", description: "Unlock seamless token transfers from contracts; learn, code, and deploy.", link: "/tutorials/transfer-tokens", - type: "EVM", }, { title: "Transfer Tokens with Data", description: "Go beyond basic transfers with logic-infused token movements in your EVM contracts.", link: "/tutorials/transfer-tokens-data", - type: "EVM", }, { title: "Using the Token Manager", description: "Effortlessly manage CCTs by tracking, importing and organizing tokens from your dashboard.", link: "/tutorials/token-manager", - type: "EVM", }, { title: "Using the JS SDK", description: "Integrate CCIP in your frontend or backend effortlessly with JavaScript SDK.", link: "/tutorials/js-sdk", - type: "EVM", }, { title: "Check Message Status", description: "Retrieve real-time status of your offchain transaction from EVM.", link: "/tutorials/check-message-status", - type: "EVM", }, { title: "Transfer Tokens Between EOAs", description: "Send tokens offchain from an Externally Owned Account with clear steps.", link: "/tutorials/transfer-tokens-eoa", - type: "EVM", }, { title: "Using the CLI", description: "Use offchain tools from CCIP to simplify your Ethereum workflows.", link: "/tutorials/cli", - type: "EVM", }, { 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", - type: "EVM", }, { 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", - type: "EVM", }, { 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", - type: "EVM", }, { 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", - type: "EVM", }, ], }, @@ -115,13 +103,11 @@ const exampleTutorials = [ title: "Getting Started with Solana", description: "Learn the basics of building on Solana blockchain.", link: "/tutorials/solana-getting-started", - type: "Solana", }, { title: "Solana Token Transfers", description: "Transfer tokens on the Solana blockchain.", link: "/tutorials/solana-transfers", - type: "Solana", }, ], }, @@ -132,7 +118,6 @@ const exampleTutorials = [ title: "Getting Started with Aptos", description: "Start building on the Aptos blockchain.", link: "/tutorials/aptos-getting-started", - type: "Aptos", }, ], }, @@ -150,7 +135,7 @@ const exampleTutorials = [
- +
From 326071074f5b24a23d588ffe663455810d4be22a Mon Sep 17 00:00:00 2001 From: Tyrel Chambers Date: Fri, 17 Oct 2025 14:00:03 -0400 Subject: [PATCH 5/9] Update index.astro --- src/pages/ccip/index.astro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ccip/index.astro b/src/pages/ccip/index.astro index 9eacf7c22f4..011b7d67a8d 100644 --- a/src/pages/ccip/index.astro +++ b/src/pages/ccip/index.astro @@ -11,4 +11,4 @@ if (!entry) { const { Content, headings } = await render(entry) --- - + From f681b91f647f1b22d7e6ac88761716b808db30e2 Mon Sep 17 00:00:00 2001 From: Tyrel Chambers Date: Fri, 17 Oct 2025 14:04:20 -0400 Subject: [PATCH 6/9] remove unused import --- src/components/TabGrid/GridCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TabGrid/GridCard.tsx b/src/components/TabGrid/GridCard.tsx index b50910450dd..8fe64c59d3f 100644 --- a/src/components/TabGrid/GridCard.tsx +++ b/src/components/TabGrid/GridCard.tsx @@ -1,4 +1,4 @@ -import { Typography, SvgArrowDiagonalUpperRight } from "@chainlink/blocks" +import { Typography } from "@chainlink/blocks" import styles from "./GridCard.module.css" export interface GridItem { From be5128b15fdb138bc076acbbe5a79f1b75ed0926 Mon Sep 17 00:00:00 2001 From: Tyrel Chambers Date: Fri, 17 Oct 2025 14:10:15 -0400 Subject: [PATCH 7/9] Update GridCard.module.css --- src/components/TabGrid/GridCard.module.css | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/components/TabGrid/GridCard.module.css b/src/components/TabGrid/GridCard.module.css index ab174828480..0d56d2945c4 100644 --- a/src/components/TabGrid/GridCard.module.css +++ b/src/components/TabGrid/GridCard.module.css @@ -16,8 +16,8 @@ .cardFooter { opacity: 0; - /* enforcing a width */ margin-top: auto; + /* enforcing a width */ min-width: 16px; } @@ -39,17 +39,3 @@ line-height: 1.6; margin: 0; } - -@media (max-width: 768px) { - .card { - padding: 1.25rem; - } - - .cardTitle { - font-size: 1rem; - } - - .cardDescription { - font-size: 0.875rem; - } -} From 98818b0f5c3843af603ac207eb648e8a88b5c323 Mon Sep 17 00:00:00 2001 From: Tyrel Chambers Date: Mon, 20 Oct 2025 13:52:45 -0400 Subject: [PATCH 8/9] change how we add borders --- src/components/TabGrid/GridCard.module.css | 18 ++++++++++++++++++ src/components/TabGrid/GridCard.tsx | 22 +++------------------- src/components/TabGrid/ItemGrid.tsx | 6 +++--- src/components/TabGrid/TabGrid.module.css | 10 ++++++++-- src/components/TabGrid/TabGrid.tsx | 10 +++++----- 5 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/components/TabGrid/GridCard.module.css b/src/components/TabGrid/GridCard.module.css index 0d56d2945c4..01e85b78580 100644 --- a/src/components/TabGrid/GridCard.module.css +++ b/src/components/TabGrid/GridCard.module.css @@ -4,12 +4,30 @@ padding: var(--space-6x); align-items: start; gap: var(--space-6x); + border-right: 1px solid var(--border); + border-bottom: 1px solid var(--border); &:hover .cardFooter { opacity: 1; } } +[data-columns="1"] > .card:nth-child(1) { + border-top: 1px solid var(--border); +} + +[data-columns="2"] > .card:nth-child(-n + 2) { + border-top: 1px solid var(--border); +} + +[data-columns="3"] > .card:nth-child(-n + 3) { + border-top: 1px solid var(--border); +} + +[data-columns="4"] > .card:nth-child(-n + 4) { + border-top: 1px solid var(--border); +} + .card:hover { background-color: var(--muted); } diff --git a/src/components/TabGrid/GridCard.tsx b/src/components/TabGrid/GridCard.tsx index 8fe64c59d3f..e83883d8426 100644 --- a/src/components/TabGrid/GridCard.tsx +++ b/src/components/TabGrid/GridCard.tsx @@ -5,29 +5,13 @@ 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)", - } - +export const GridCard = ({ title, description, link }: GridItem) => { return ( - +
-

{title}

+

{title}

{description} diff --git a/src/components/TabGrid/ItemGrid.tsx b/src/components/TabGrid/ItemGrid.tsx index 0a1fd2966ba..0a2ed5e92d3 100644 --- a/src/components/TabGrid/ItemGrid.tsx +++ b/src/components/TabGrid/ItemGrid.tsx @@ -3,14 +3,14 @@ import styles from "./TabGrid.module.css" interface ItemGridProps { links: GridItem[] - columns?: number + columns?: 1 | 2 | 3 | 4 } export const ItemGrid = ({ links, columns = 3 }: ItemGridProps) => { return ( -
+
{links.map((link, index) => ( - + ))}
) diff --git a/src/components/TabGrid/TabGrid.module.css b/src/components/TabGrid/TabGrid.module.css index 815357b55c5..04290af354e 100644 --- a/src/components/TabGrid/TabGrid.module.css +++ b/src/components/TabGrid/TabGrid.module.css @@ -1,8 +1,9 @@ .grid { display: grid; + border-left: 1px solid var(--border); } -.tutorialHeader { +.gridHeader { display: flex; justify-content: space-between; align-items: center; @@ -27,11 +28,16 @@ background-color: var(--pill-active); border-color: var(--pill-active); - & p { + & h3 { color: var(--pill-active-foreground); } } +.tabTitle { + color: var(--pill-foreground); + font-weight: 400; +} + .tabsList { display: flex; gap: var(--space-2x); diff --git a/src/components/TabGrid/TabGrid.tsx b/src/components/TabGrid/TabGrid.tsx index 6934972b85f..6e85ec3cfa6 100644 --- a/src/components/TabGrid/TabGrid.tsx +++ b/src/components/TabGrid/TabGrid.tsx @@ -11,18 +11,18 @@ export interface Tab { interface TabGridProps { tabs: Tab[] header: string - columns?: number + columns?: 1 | 2 | 3 | 4 } -export const TabGrid = ({ tabs, header, columns }: TabGridProps) => { +export const TabGrid = ({ tabs, header, columns = 3 }: TabGridProps) => { return ( -
+
{header} {tabs.map((tab) => ( - {tab.name} +

{tab.name}

))}
@@ -30,7 +30,7 @@ export const TabGrid = ({ tabs, header, columns }: TabGridProps) => { {tabs.map((tab) => ( -
+
From 6ba9de7b126be121134315ebae8d460a85c0676c Mon Sep 17 00:00:00 2001 From: Tyrel Chambers Date: Mon, 20 Oct 2025 13:55:58 -0400 Subject: [PATCH 9/9] make grid responsive --- src/components/TabGrid/GridCard.module.css | 24 ++++++++++++++++++++++ src/components/TabGrid/TabGrid.module.css | 11 +++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/components/TabGrid/GridCard.module.css b/src/components/TabGrid/GridCard.module.css index 01e85b78580..4c4e1b2626e 100644 --- a/src/components/TabGrid/GridCard.module.css +++ b/src/components/TabGrid/GridCard.module.css @@ -28,6 +28,30 @@ border-top: 1px solid var(--border); } +/* Tablet: adjust border-top for 2-column layouts */ +@media (max-width: 1024px) { + [data-columns="3"] > .card:nth-child(-n + 3), + [data-columns="4"] > .card:nth-child(-n + 4) { + border-top: none; + } + + [data-columns="3"] > .card:nth-child(-n + 2), + [data-columns="4"] > .card:nth-child(-n + 2) { + border-top: 1px solid var(--border); + } +} + +/* Mobile: single column - only first card has border-top */ +@media (max-width: 768px) { + [data-columns] > .card:nth-child(n) { + border-top: none; + } + + [data-columns] > .card:nth-child(1) { + border-top: 1px solid var(--border); + } +} + .card:hover { background-color: var(--muted); } diff --git a/src/components/TabGrid/TabGrid.module.css b/src/components/TabGrid/TabGrid.module.css index 04290af354e..d20535265a2 100644 --- a/src/components/TabGrid/TabGrid.module.css +++ b/src/components/TabGrid/TabGrid.module.css @@ -44,8 +44,17 @@ border-bottom: 0; } +/* Tablet: reduce columns to 2 for 3+ column layouts */ +@media (max-width: 1024px) { + [data-columns="3"], + [data-columns="4"] { + grid-template-columns: repeat(2, 1fr) !important; + } +} + +/* Mobile: single column for all layouts */ @media (max-width: 768px) { .grid { - grid-template-columns: 1fr; + grid-template-columns: 1fr !important; } }