Skip to content

Commit 856c50d

Browse files
Feat/93 layout tutorials (#104)
* add tutorial foundation * Add tutorial components * add README * support N number of columns and add arrow icon * Update index.astro * remove unused import * Update GridCard.module.css * change how we add borders * make grid responsive --------- Co-authored-by: Simone Cuomo <[email protected]>
1 parent 2c2c2ea commit 856c50d

File tree

8 files changed

+413
-0
lines changed

8 files changed

+413
-0
lines changed
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
.card {
2+
display: flex;
3+
background: var(--color-background);
4+
padding: var(--space-6x);
5+
align-items: start;
6+
gap: var(--space-6x);
7+
border-right: 1px solid var(--border);
8+
border-bottom: 1px solid var(--border);
9+
10+
&:hover .cardFooter {
11+
opacity: 1;
12+
}
13+
}
14+
15+
[data-columns="1"] > .card:nth-child(1) {
16+
border-top: 1px solid var(--border);
17+
}
18+
19+
[data-columns="2"] > .card:nth-child(-n + 2) {
20+
border-top: 1px solid var(--border);
21+
}
22+
23+
[data-columns="3"] > .card:nth-child(-n + 3) {
24+
border-top: 1px solid var(--border);
25+
}
26+
27+
[data-columns="4"] > .card:nth-child(-n + 4) {
28+
border-top: 1px solid var(--border);
29+
}
30+
31+
/* Tablet: adjust border-top for 2-column layouts */
32+
@media (max-width: 1024px) {
33+
[data-columns="3"] > .card:nth-child(-n + 3),
34+
[data-columns="4"] > .card:nth-child(-n + 4) {
35+
border-top: none;
36+
}
37+
38+
[data-columns="3"] > .card:nth-child(-n + 2),
39+
[data-columns="4"] > .card:nth-child(-n + 2) {
40+
border-top: 1px solid var(--border);
41+
}
42+
}
43+
44+
/* Mobile: single column - only first card has border-top */
45+
@media (max-width: 768px) {
46+
[data-columns] > .card:nth-child(n) {
47+
border-top: none;
48+
}
49+
50+
[data-columns] > .card:nth-child(1) {
51+
border-top: 1px solid var(--border);
52+
}
53+
}
54+
55+
.card:hover {
56+
background-color: var(--muted);
57+
}
58+
59+
.cardFooter {
60+
opacity: 0;
61+
margin-top: auto;
62+
/* enforcing a width */
63+
min-width: 16px;
64+
}
65+
66+
.cardFooter img {
67+
width: 10px;
68+
height: 10px;
69+
}
70+
71+
.cardTitle {
72+
font-size: 16px;
73+
font-weight: 525;
74+
color: var(--foreground);
75+
margin-bottom: var(--space-2x);
76+
}
77+
78+
.cardDescription {
79+
color: var(--color-text-secondary);
80+
font-size: 0.9375rem;
81+
line-height: 1.6;
82+
margin: 0;
83+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Typography } from "@chainlink/blocks"
2+
import styles from "./GridCard.module.css"
3+
4+
export interface GridItem {
5+
title: string
6+
description: string
7+
link: string
8+
}
9+
10+
export const GridCard = ({ title, description, link }: GridItem) => {
11+
return (
12+
<a href={link} className={styles.card}>
13+
<div>
14+
<p className={styles.cardTitle}>{title}</p>
15+
<Typography variant="body-s" style={{ lineHeight: "24px" }}>
16+
{description}
17+
</Typography>
18+
</div>
19+
20+
<div className={styles.cardFooter}>
21+
<img src="/assets/icons/upper-right-arrow.svg" alt="arrow" />
22+
</div>
23+
</a>
24+
)
25+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { GridCard, GridItem } from "./GridCard.tsx"
2+
import styles from "./TabGrid.module.css"
3+
4+
interface ItemGridProps {
5+
links: GridItem[]
6+
columns?: 1 | 2 | 3 | 4
7+
}
8+
9+
export const ItemGrid = ({ links, columns = 3 }: ItemGridProps) => {
10+
return (
11+
<div className={styles.grid} style={{ gridTemplateColumns: `repeat(${columns}, 1fr)` }} data-columns={columns}>
12+
{links.map((link, index) => (
13+
<GridCard key={`${link.title}-${index}`} {...link} />
14+
))}
15+
</div>
16+
)
17+
}

src/components/TabGrid/README.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# TabGrid Component
2+
3+
A tabbed interface for displaying grid items organized by category.
4+
5+
## What is this?
6+
7+
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.
8+
9+
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.
10+
11+
## Usage
12+
13+
```tsx
14+
import { TabGrid } from "@components/TabGrid/TabGrid"
15+
;<TabGrid
16+
header="Tutorials"
17+
tabs={[
18+
{
19+
name: "Getting Started",
20+
links: [
21+
{
22+
title: "Quick Start Guide",
23+
description: "Learn the basics in 5 minutes",
24+
link: "/docs/quickstart",
25+
},
26+
{
27+
title: "Installation",
28+
description: "Set up your development environment",
29+
link: "/docs/installation",
30+
},
31+
],
32+
},
33+
{
34+
name: "Advanced",
35+
links: [
36+
{
37+
title: "Architecture Overview",
38+
description: "Understand the system design",
39+
link: "/docs/architecture",
40+
},
41+
],
42+
},
43+
]}
44+
/>
45+
```
46+
47+
## How to set it up
48+
49+
The component requires a `tabs` prop, which is an array of tab objects. Each tab object contains:
50+
51+
- A **name** (the label shown on the tab button)
52+
- A list of **links** (the items shown when that tab is active)
53+
54+
Each grid item needs three pieces of information:
55+
56+
- **title** - The name of the item
57+
- **description** - A short sentence explaining what the item covers
58+
- **link** - The URL where the item can be found
59+
60+
## Props Reference
61+
62+
### `TabGrid`
63+
64+
| Prop | Type | Required | Description |
65+
| --------- | -------- | -------- | ------------------------------------------------- |
66+
| `header` | `string` | Yes | The heading text displayed above the tabs |
67+
| `tabs` | `Tab[]` | Yes | List of tabs, each containing a category of items |
68+
| `columns` | `number` | No | Number of columns in the grid (defaults to 2) |
69+
70+
### `Tab`
71+
72+
| Property | Type | Required | Description |
73+
| -------- | ------------ | -------- | -------------------------------------------------------- |
74+
| `name` | `string` | Yes | The label displayed on the tab (e.g., "Getting Started") |
75+
| `links` | `GridItem[]` | Yes | The list of items to show when this tab is selected |
76+
77+
### `GridItem`
78+
79+
| Property | Type | Required | Description |
80+
| ------------- | -------- | -------- | -------------------------------------------- |
81+
| `title` | `string` | Yes | The item's heading |
82+
| `description` | `string` | Yes | A brief explanation of what users will learn |
83+
| `link` | `string` | Yes | The URL path to the item page |
84+
85+
## Components
86+
87+
- **TabGrid** - Main container with tabs and header
88+
- **ItemGrid** - Grid layout for item cards
89+
- **GridCard** - Individual item card with hover effects
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
.grid {
2+
display: grid;
3+
border-left: 1px solid var(--border);
4+
}
5+
6+
.gridHeader {
7+
display: flex;
8+
justify-content: space-between;
9+
align-items: center;
10+
margin-bottom: var(--space-8x);
11+
}
12+
13+
.tabsTrigger {
14+
height: 32px;
15+
padding: var(--space-1x) var(--space-2x);
16+
justify-content: center;
17+
align-items: center;
18+
border-radius: var(--space-2x);
19+
background-color: var(--pill);
20+
border: 1px solid var(--pill-border);
21+
}
22+
23+
.tabsTrigger:hover {
24+
background-color: var(--pill-hover);
25+
}
26+
27+
.tabsTrigger[data-state="active"] {
28+
background-color: var(--pill-active);
29+
border-color: var(--pill-active);
30+
31+
& h3 {
32+
color: var(--pill-active-foreground);
33+
}
34+
}
35+
36+
.tabTitle {
37+
color: var(--pill-foreground);
38+
font-weight: 400;
39+
}
40+
41+
.tabsList {
42+
display: flex;
43+
gap: var(--space-2x);
44+
border-bottom: 0;
45+
}
46+
47+
/* Tablet: reduce columns to 2 for 3+ column layouts */
48+
@media (max-width: 1024px) {
49+
[data-columns="3"],
50+
[data-columns="4"] {
51+
grid-template-columns: repeat(2, 1fr) !important;
52+
}
53+
}
54+
55+
/* Mobile: single column for all layouts */
56+
@media (max-width: 768px) {
57+
.grid {
58+
grid-template-columns: 1fr !important;
59+
}
60+
}

src/components/TabGrid/TabGrid.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import styles from "./TabGrid.module.css"
2+
import { GridItem } from "./GridCard.tsx"
3+
import { ItemGrid } from "./ItemGrid.tsx"
4+
import { Tabs, TabsContent, TabsList, TabsTrigger, Typography } from "@chainlink/blocks"
5+
6+
export interface Tab {
7+
name: string
8+
links: GridItem[]
9+
}
10+
11+
interface TabGridProps {
12+
tabs: Tab[]
13+
header: string
14+
columns?: 1 | 2 | 3 | 4
15+
}
16+
17+
export const TabGrid = ({ tabs, header, columns = 3 }: TabGridProps) => {
18+
return (
19+
<Tabs defaultValue={tabs[0].name}>
20+
<header className={styles.gridHeader}>
21+
<Typography variant="h2">{header}</Typography>
22+
<TabsList className={styles.tabsList}>
23+
{tabs.map((tab) => (
24+
<TabsTrigger key={tab.name} value={tab.name} className={styles.tabsTrigger}>
25+
<h3 className={styles.tabTitle}>{tab.name}</h3>
26+
</TabsTrigger>
27+
))}
28+
</TabsList>
29+
</header>
30+
31+
{tabs.map((tab) => (
32+
<TabsContent key={tab.name} value={tab.name}>
33+
<div className={styles.gridContent}>
34+
<ItemGrid links={tab.links} columns={columns} />
35+
</div>
36+
</TabsContent>
37+
))}
38+
</Tabs>
39+
)
40+
}

0 commit comments

Comments
 (0)