Skip to content

Commit ddf4936

Browse files
authored
Landing Page update (#173)
* Add Grid component and refactor NetworkGrid and TokenGrid to use it * Refactor card components to use a unified Card component and update styles for consistency * Add LazyVerifierGrid component and integrate verifiers section in CCIP landing page * Enhance Grid and SeeMore components with href support and adjust grid gap for better layout * Add AddButton component and integrate it into Chain and CCIP landing pages; include add icon SVG and update theme styles for consistency * Refactor AddButton styles for consistency; use CSS variables for spacing and dimensions * fix lint * fix lint
1 parent 1ae8552 commit ddf4936

File tree

19 files changed

+380
-113
lines changed

19 files changed

+380
-113
lines changed

public/assets/icons/add.svg

Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
export interface Props {
3+
href: string
4+
text: string
5+
}
6+
7+
const { href, text } = Astro.props
8+
---
9+
10+
<a class="add-button" href={href}>
11+
<img src="/assets/icons/add.svg" alt="Add" class="add-button-icon" />
12+
{text}
13+
</a>
14+
15+
<style>
16+
.add-button {
17+
display: inline-flex;
18+
align-items: center;
19+
gap: var(--space-2x);
20+
padding: var(--space-2x) var(--space-4x);
21+
border: 1px solid var(--tertiary-border);
22+
border-radius: var(--space-1x);
23+
font-family: "Inter", sans-serif;
24+
font-size: var(--space-3x);
25+
font-weight: 600;
26+
line-height: var(--space-4x);
27+
color: var(--tertiary-foreground);
28+
text-decoration: none;
29+
background-color: transparent;
30+
transition: all 0.2s ease;
31+
}
32+
33+
.add-button:hover {
34+
background-color: var(--gray-100);
35+
border-color: var(--gray-400);
36+
}
37+
38+
.add-button-icon {
39+
width: var(--space-3x);
40+
height: var(--space-3x);
41+
}
42+
</style>

src/components/CCIP/Cards/Card.css

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
.card__container {
2+
display: flex;
3+
padding: var(--space-6x);
4+
gap: var(--space-3x);
5+
width: 100%;
6+
background: var(--white);
7+
border: 1px solid var(--gray-200);
8+
border-radius: var(--space-1x);
9+
/* Optimize rendering performance */
10+
contain: layout style paint;
11+
will-change: background-color;
12+
}
13+
14+
.card__container:hover {
15+
background-color: var(--gray-50);
16+
}
17+
18+
.card__container img,
19+
.card__container object,
20+
.card__container object img {
21+
width: var(--space-10x);
22+
height: var(--space-10x);
23+
margin-top: auto;
24+
margin-bottom: auto;
25+
}
26+
27+
.card__container h3 {
28+
font-size: var(--space-4x);
29+
font-weight: var(--font-weight-medium);
30+
line-height: var(--space-6x);
31+
color: var(--gray-950);
32+
margin-bottom: var(--space-1x);
33+
}
34+
35+
.card__container p {
36+
margin-bottom: 0;
37+
font-size: var(--space-3x);
38+
line-height: var(--space-5x);
39+
color: var(--gray-500);
40+
}

src/components/CCIP/Cards/Card.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { memo, type ReactNode } from "react"
2+
import "./Card.css"
3+
4+
interface CardProps {
5+
logo: ReactNode
6+
title: string
7+
subtitle?: string
8+
link?: string
9+
onClick?: () => void
10+
ariaLabel?: string
11+
}
12+
13+
const Card = memo(function Card({ logo, title, subtitle, link, onClick, ariaLabel }: CardProps) {
14+
const content = (
15+
<>
16+
{logo}
17+
<div>
18+
<h3>{title}</h3>
19+
{subtitle && <p>{subtitle}</p>}
20+
</div>
21+
</>
22+
)
23+
24+
if (link) {
25+
return (
26+
<a href={link} aria-label={ariaLabel}>
27+
<div className="card__container">{content}</div>
28+
</a>
29+
)
30+
}
31+
32+
if (onClick) {
33+
return (
34+
<button type="button" className="card__container" onClick={onClick} aria-label={ariaLabel || title}>
35+
{content}
36+
</button>
37+
)
38+
}
39+
40+
return <div className="card__container">{content}</div>
41+
})
42+
43+
export default Card
Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { memo } from "react"
2-
import "./NetworkCard.css"
2+
import Card from "./Card.tsx"
33

44
interface NetworkCardProps {
55
name: string
@@ -9,17 +9,9 @@ interface NetworkCardProps {
99
}
1010

1111
const NetworkCard = memo(function NetworkCard({ name, totalLanes, totalTokens, logo }: NetworkCardProps) {
12-
return (
13-
<div className="network-card__container">
14-
<img src={logo} alt="" loading="lazy" />
15-
<div>
16-
<h3>{name}</h3>
17-
<p>
18-
{totalLanes} {totalLanes > 1 ? "lanes" : "lane"} | {totalTokens} {totalTokens > 1 ? "tokens" : "token"}
19-
</p>
20-
</div>
21-
</div>
22-
)
12+
const subtitle = `${totalLanes} ${totalLanes === 1 ? "lane" : "lanes"} | ${totalTokens} ${totalTokens === 1 ? "token" : "tokens"}`
13+
14+
return <Card logo={<img src={logo} alt="" loading="lazy" />} title={name} subtitle={subtitle} />
2315
})
2416

2517
export default NetworkCard

src/components/CCIP/Cards/TokenCard.css

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,11 @@
11
.token-card__container {
22
display: flex;
3-
width: 100%;
4-
height: 110px;
5-
min-width: 110px;
6-
margin: 0 auto;
7-
flex-direction: column;
8-
align-items: center;
9-
text-align: center;
10-
padding: var(--space-4x);
3+
padding: var(--space-6x);
114
gap: var(--space-3x);
12-
background: #ffffff;
5+
width: 100%;
6+
background: var(--white);
137
border: 1px solid var(--gray-200);
148
border-radius: var(--space-1x);
15-
justify-content: center;
16-
cursor: pointer;
179
/* Optimize rendering performance */
1810
contain: layout style paint;
1911
will-change: background-color;
@@ -27,14 +19,24 @@
2719
.token-card__container object img {
2820
width: var(--space-10x);
2921
height: var(--space-10x);
22+
margin-top: auto;
23+
margin-bottom: auto;
3024
border-radius: 50%;
3125
}
3226

3327
.token-card__container h3 {
3428
font-size: var(--space-4x);
35-
font-weight: 500;
29+
font-weight: var(--font-weight-medium);
30+
line-height: var(--space-6x);
3631
color: var(--gray-950);
32+
margin-bottom: var(--space-1x);
33+
}
34+
35+
.token-card__container p {
3736
margin-bottom: 0;
37+
font-size: var(--space-3x);
38+
line-height: var(--space-5x);
39+
color: var(--gray-500);
3840
}
3941

4042
.truncate {

src/components/CCIP/Cards/TokenCard.tsx

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,35 @@
11
import { memo } from "react"
22
import { fallbackTokenIconUrl } from "~/features/utils/index.ts"
3+
import Card from "./Card.tsx"
34
import "./TokenCard.css"
45

56
interface TokenCardProps {
67
id: string
78
logo?: string
89
link?: string
910
onClick?: () => void
11+
totalNetworks?: number
1012
}
1113

12-
const TokenCard = memo(function TokenCard({ id, logo, link, onClick }: TokenCardProps) {
13-
if (link) {
14-
return (
15-
<a href={link}>
16-
<div className="token-card__container">
17-
{/* We cannot use the normal Image/onError syntax as a fallback as the element is server rendered
18-
and the onerror does not seem to work correctly. Using Picture will also not work. */}
19-
<object data={logo} type="image/png" aria-label={`${id} token logo`}>
20-
<img src={fallbackTokenIconUrl} alt={`${id} token logo`} loading="lazy" />
21-
</object>
22-
<h3>{id}</h3>
23-
</div>
24-
</a>
25-
)
26-
}
14+
const TokenCard = memo(function TokenCard({ id, logo, link, onClick, totalNetworks }: TokenCardProps) {
15+
const logoElement = (
16+
<object data={logo} type="image/png" aria-label={`${id} token logo`}>
17+
<img src={fallbackTokenIconUrl} alt={`${id} token logo`} loading="lazy" />
18+
</object>
19+
)
2720

28-
if (onClick) {
29-
return (
30-
<button type="button" className="token-card__container" onClick={onClick} aria-label={`View ${id} token details`}>
31-
<object data={logo} type="image/png" aria-label={`${id} token logo`}>
32-
<img src={fallbackTokenIconUrl} alt={`${id} token logo`} loading="lazy" />
33-
</object>
34-
<h3>{id}</h3>
35-
</button>
36-
)
37-
}
21+
const subtitle =
22+
totalNetworks !== undefined ? `${totalNetworks} ${totalNetworks === 1 ? "network" : "networks"}` : undefined
3823

3924
return (
40-
<div className="token-card__container">
41-
<object data={logo} type="image/png">
42-
<img src={fallbackTokenIconUrl} alt="" loading="lazy" />
43-
</object>
44-
<h3>{id}</h3>
45-
</div>
25+
<Card
26+
logo={logoElement}
27+
title={id}
28+
subtitle={subtitle}
29+
link={link}
30+
onClick={onClick}
31+
ariaLabel={`View ${id} token details`}
32+
/>
4633
)
4734
})
4835

src/components/CCIP/Chain/Chain.astro

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import ChainTokenGrid from "./ChainTokenGrid"
1717
import { generateChainStructuredData } from "~/utils/ccipStructuredData"
1818
import StructuredData from "~/components/StructuredData.astro"
1919
import { DOCS_BASE_URL } from "~/utils/structuredData"
20+
import AddButton from "~/components/CCIP/AddButton/AddButton.astro"
2021
2122
interface Props {
2223
environment: Environment
@@ -128,14 +129,7 @@ const chainStructuredData = generateChainStructuredData(
128129
<h2>Tokens <span>({allTokens.length})</span></h2>
129130
{
130131
network.chainType !== "solana" && network.chainType !== "aptos" && (
131-
<a class="button secondary" href="/ccip/tutorials/evm/token-manager#verifying-your-token">
132-
<img
133-
src="/assets/icons/plus.svg"
134-
alt="Add"
135-
style={{ width: "1em", height: "1em", marginRight: "0.5em", verticalAlign: "middle" }}
136-
/>
137-
Add my token
138-
</a>
132+
<AddButton href="/ccip/tutorials/evm/token-manager#verifying-your-token" text="Add my token" />
139133
)
140134
}
141135
</div>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.grid {
2+
display: grid;
3+
grid-template-columns: 1fr;
4+
gap: var(--space-2x);
5+
}
6+
7+
@media (min-width: 992px) {
8+
.grid {
9+
grid-template-columns: 1fr 1fr 1fr 1fr;
10+
gap: var(--space-4x);
11+
}
12+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useState, type ReactNode } from "react"
2+
import SeeMore from "../SeeMore/SeeMore.tsx"
3+
import "./Grid.css"
4+
5+
interface GridProps {
6+
items: any[]
7+
renderItem: (item: any, index: number) => ReactNode
8+
initialDisplayCount: number
9+
seeMoreLabel: string
10+
className?: string
11+
seeMoreLink?: string
12+
}
13+
14+
function Grid({ items, renderItem, initialDisplayCount, seeMoreLabel, className = "grid", seeMoreLink }: GridProps) {
15+
const [seeMore, setSeeMore] = useState(items.length <= initialDisplayCount)
16+
17+
return (
18+
<>
19+
<div className={className}>
20+
{items.slice(0, seeMore ? items.length : initialDisplayCount).map((item, index) => renderItem(item, index))}
21+
</div>
22+
{!seeMore && <SeeMore onClick={() => setSeeMore(!seeMore)} label={seeMoreLabel} href={seeMoreLink} />}
23+
</>
24+
)
25+
}
26+
27+
export default Grid

0 commit comments

Comments
 (0)