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
49 changes: 49 additions & 0 deletions src/components/TabGrid/ChainAwareTabGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/** @jsxImportSource react */
import { useStore } from "@nanostores/react"
import { selectedChainType } from "~/stores/chainType.js"
import styles from "./TabGrid.module.css"
import { GridItem } from "./GridCard.tsx"
import { ItemGrid } from "./ItemGrid.tsx"
import { Typography } from "@chainlink/blocks"

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

interface ChainAwareTabGridProps {
tabs: Tab[]
header: string
columns?: 1 | 2 | 3 | 4
}

export const ChainAwareTabGrid = ({ tabs, header, columns = 3 }: ChainAwareTabGridProps) => {
const activeChainType = useStore(selectedChainType)

const activeTab = tabs.find((tab) => tab.name.toLowerCase() === activeChainType.toLowerCase())

Comment on lines +23 to +24
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If tabs is an empty array, activeTab will be undefined and tabs[0] will also be undefined, causing displayTab.links on line 39 to throw a runtime error. Consider adding a guard clause or early return for empty arrays.

Suggested change
const activeTab = tabs.find((tab) => tab.name.toLowerCase() === activeChainType.toLowerCase())
const displayTab = activeTab || tabs[0]
if (!displayTab) {
return null
}

Fix it with Roo Code or mention @roomote and request a fix.

const displayTab = activeTab || tabs[0]

if (!displayTab) {
return null
}

return (
<div className={styles.tabGridWrapper}>
<header className={styles.gridHeader}>
<Typography
variant="h2"
style={{
fontSize: "32px",
}}
>
{header}
</Typography>
</header>

<div className={styles.gridContent}>
<ItemGrid links={displayTab.links} columns={columns} />
</div>
</div>
)
}
116 changes: 104 additions & 12 deletions src/components/TabGrid/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
# TabGrid Component
# TabGrid Components

A tabbed interface for displaying grid items organized by category.
Two components for displaying grid items organized by category: one with built-in tabs and one that syncs with the global chain type selector.

## What is this?
## Components

### TabGrid

A tabbed interface for displaying grid items organized by category with its own tab selector.

#### 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
#### Usage

```tsx
import { TabGrid } from "@components/TabGrid/TabGrid"
Expand Down Expand Up @@ -44,11 +50,88 @@ import { TabGrid } from "@components/TabGrid/TabGrid"
/>
```

---

### ChainAwareTabGrid

A grid component that automatically filters content based on the selected chain type from the ChainTypeSelector dropdown.

#### What is this?

The ChainAwareTabGrid component displays a collection of items filtered by the currently selected blockchain type (EVM, Solana, Aptos, etc.). Unlike the regular TabGrid which has its own tab selector, this component syncs with the global chain type selector in the DocsLayout sidebar/header.

This component is ideal for product landing pages (like CCIP) where content should automatically update based on the chain type selected in the main navigation.

#### Usage

```tsx
import { ChainAwareTabGrid } from "@components/TabGrid/ChainAwareTabGrid"

// Define tutorials for each chain type
const tutorials = [
{
name: "EVM",
links: [
{
title: "Transfer Tokens",
description: "Unlock seamless token transfers from contracts",
link: "/ccip/tutorials/evm/transfer-tokens-from-contract",
},
{
title: "Transfer Tokens with Data",
description: "Go beyond basic transfers with logic-infused token movements",
link: "/ccip/tutorials/evm/programmable-token-transfers",
},
],
},
{
name: "Solana",
links: [
{
title: "Getting Started with Solana",
description: "Learn the basics of building on Solana blockchain",
link: "/ccip/tutorials/svm",
},
{
title: "Solana Token Transfers",
description: "Transfer tokens on the Solana blockchain",
link: "/ccip/tutorials/svm/source/token-transfers",
},
],
},
{
name: "Aptos",
links: [
{
title: "Getting Started with Aptos",
description: "Start building on the Aptos blockchain",
link: "/ccip/tutorials/aptos",
},
],
},
]

;<ChainAwareTabGrid header="Tutorials" client:visible tabs={tutorials} />
```

#### How it works

1. The component subscribes to the global `selectedChainType` store
2. When the user changes the chain type using the ChainTypeSelector dropdown, the component automatically updates
3. It finds the tab matching the selected chain type (case-insensitive)
4. Displays only the content for that chain type
5. Falls back to the first tab if no match is found

#### When to use

- **Use ChainAwareTabGrid** when content should sync with the global chain type selector (e.g., product landing pages)
- **Use TabGrid** when you need independent tab navigation that doesn't relate to blockchain types

## How to set it up

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

- A **name** (the label shown on the tab button)
- A **name** (the label shown on the tab button for TabGrid, or the chain type identifier for ChainAwareTabGrid)
- A list of **links** (the items shown when that tab is active)

Each grid item needs three pieces of information:
Expand All @@ -65,14 +148,22 @@ Each grid item needs three pieces of information:
| --------- | -------- | -------- | ------------------------------------------------- |
| `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) |
| `columns` | `number` | No | Number of columns in the grid (defaults to 3) |

### `ChainAwareTabGrid`

| Prop | Type | Required | Description |
| --------- | -------- | -------- | ------------------------------------------------------------------------------ |
| `header` | `string` | Yes | The heading text displayed above the grid |
| `tabs` | `Tab[]` | Yes | List of tabs, each with a `name` matching a chain type (e.g., "EVM", "Solana") |
| `columns` | `number` | No | Number of columns in the grid (defaults to 3) |

### `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 |
| Property | Type | Required | Description |
| -------- | ------------ | -------- | -------------------------------------------------------------------------------------------- |
| `name` | `string` | Yes | For TabGrid: any label. For ChainAwareTabGrid: must match chain type (e.g., "EVM", "Solana") |
| `links` | `GridItem[]` | Yes | The list of items to show when this tab is selected |

### `GridItem`

Expand All @@ -85,6 +176,7 @@ Each grid item needs three pieces of information:

## Components

- **TabGrid** - Main container with tabs and header
- **TabGrid** - Main container with tabs and header (includes tab selector)
- **ChainAwareTabGrid** - Main container that syncs with global chain type selector (no built-in tabs)
- **ItemGrid** - Grid layout for item cards
- **GridCard** - Individual item card with hover effects
4 changes: 2 additions & 2 deletions src/content/ccip/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ hideTitle: true
---

import LayoutHero from "@components/LayoutHero/LayoutHero.astro"
import { TabGrid } from "@components/TabGrid/TabGrid.tsx"
import { ChainAwareTabGrid } from "@components/TabGrid/ChainAwareTabGrid.tsx"
import ResourceSection from "@components/Resource/ResourceSection.astro"
import QuickLinkCard from "@components/QuickLinkCard/QuickLinkCard.astro"
import ToolsUtilitiesGrid from "@components/ToolsUtilitiesGrid/ToolsUtilitiesGrid.astro"
Expand Down Expand Up @@ -297,7 +297,7 @@ export const cardLinks = [

<OverviewWrapper>
<CardsWrapper links={cardLinks} />
<TabGrid header="Tutorials" client:visible tabs={exampleTutorials} />
<ChainAwareTabGrid header="Tutorials" client:visible tabs={exampleTutorials} />
<QuickLinkCard links={quickLinks} />
<ToolsUtilitiesGrid links={toolsAndUtilities} />
<ResourceSection title="Resources" resources={exampleResources} />
Expand Down
12 changes: 6 additions & 6 deletions src/styles/migrated.css
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed some inconsistent styling, hence this change. (Specifically remix-callout buttons)

Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,37 @@
border-radius: 6px;
box-sizing: border-box;
text-decoration: none !important;
color: #ffffff;
color: #ffffff !important;
background-color: #375bd2;
text-align: center;
transition: all 0.2s;
}

.remix-callout > a:hover {
color: #ffffff;
color: #ffffff !important;
background-color: #1a2b6b;
border: 2px solid var(--gray-900);
}

.remix-callout > a:active {
color: #ffffff;
color: #ffffff !important;
background-color: var(--gray-900);
border: 2px solid var(--gray-900);
}

.remix-callout > a:nth-of-type(n + 2) {
color: #375bd2;
color: #375bd2 !important;
background-color: #ffffff;
}

.remix-callout > a:nth-of-type(n + 2):hover {
color: #1a2b6b;
color: #1a2b6b !important;
background-color: #f5f7fd;
border: 2px solid #1a2b6b;
}

.remix-callout > a:nth-of-type(n + 2):active {
color: var(--gray-900);
color: var(--gray-900) !important;
background-color: #f5f7fd;
border: 2px solid var(--gray-900);
}
Loading