Skip to content
Open
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
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.
83 changes: 83 additions & 0 deletions src/components/TabGrid/GridCard.module.css
Original file line number Diff line number Diff line change
@@ -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;
}
25 changes: 25 additions & 0 deletions src/components/TabGrid/GridCard.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<a href={link} className={styles.card}>
<div>
<p className={styles.cardTitle}>{title}</p>
<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?: 1 | 2 | 3 | 4
}

export const ItemGrid = ({ links, columns = 3 }: ItemGridProps) => {
return (
<div className={styles.grid} style={{ gridTemplateColumns: `repeat(${columns}, 1fr)` }} data-columns={columns}>
{links.map((link, index) => (
<GridCard key={`${link.title}-${index}`} {...link} />
))}
</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
60 changes: 60 additions & 0 deletions src/components/TabGrid/TabGrid.module.css
Original file line number Diff line number Diff line change
@@ -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;
}
}
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?: 1 | 2 | 3 | 4
}

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

{tabs.map((tab) => (
<TabsContent key={tab.name} value={tab.name}>
<div className={styles.gridContent}>
<ItemGrid links={tab.links} columns={columns} />
</div>
</TabsContent>
))}
</Tabs>
)
}
Loading
Loading