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/TabGrid/GridCard.module.css b/src/components/TabGrid/GridCard.module.css new file mode 100644 index 00000000000..4c4e1b2626e --- /dev/null +++ b/src/components/TabGrid/GridCard.module.css @@ -0,0 +1,83 @@ +.card { + display: flex; + background: var(--color-background); + 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); +} + +/* 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); +} + +.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; +} diff --git a/src/components/TabGrid/GridCard.tsx b/src/components/TabGrid/GridCard.tsx new file mode 100644 index 00000000000..e83883d8426 --- /dev/null +++ b/src/components/TabGrid/GridCard.tsx @@ -0,0 +1,25 @@ +import { Typography } from "@chainlink/blocks" +import styles from "./GridCard.module.css" + +export interface GridItem { + title: string + description: string + link: string +} + +export const GridCard = ({ title, description, link }: GridItem) => { + return ( + +
+

{title}

+ + {description} + +
+ +
+ arrow +
+
+ ) +} diff --git a/src/components/TabGrid/ItemGrid.tsx b/src/components/TabGrid/ItemGrid.tsx new file mode 100644 index 00000000000..0a2ed5e92d3 --- /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?: 1 | 2 | 3 | 4 +} + +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..d20535265a2 --- /dev/null +++ b/src/components/TabGrid/TabGrid.module.css @@ -0,0 +1,60 @@ +.grid { + display: grid; + border-left: 1px solid var(--border); +} + +.gridHeader { + 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); + + & h3 { + color: var(--pill-active-foreground); + } +} + +.tabTitle { + color: var(--pill-foreground); + font-weight: 400; +} + +.tabsList { + display: flex; + gap: var(--space-2x); + 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 !important; + } +} diff --git a/src/components/TabGrid/TabGrid.tsx b/src/components/TabGrid/TabGrid.tsx new file mode 100644 index 00000000000..6e85ec3cfa6 --- /dev/null +++ b/src/components/TabGrid/TabGrid.tsx @@ -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?: 1 | 2 | 3 | 4 +} + +export const TabGrid = ({ tabs, header, columns = 3 }: TabGridProps) => { + return ( + +
+ {header} + + {tabs.map((tab) => ( + +

{tab.name}

+
+ ))} +
+
+ + {tabs.map((tab) => ( + +
+ +
+
+ ))} +
+ ) +} diff --git a/src/layouts/DocsV3Layout/DocsV3Layout.astro b/src/layouts/DocsV3Layout/DocsV3Layout.astro index 2f3e272954a..8169548be7a 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 { TabGrid } from "~/components/TabGrid/TabGrid" interface Props { frontmatter: BaseFrontmatter @@ -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", + }, + ], + }, +] --- @@ -40,6 +135,7 @@ const includeLinkToWalletScript = !!Astro.props.frontmatter.metadata?.linkToWall
+
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) --- - +