diff --git a/src/assets/role-icons/concepts-icon.svg b/src/assets/role-icons/concepts-icon.svg new file mode 100644 index 00000000000..5c6ed39319a --- /dev/null +++ b/src/assets/role-icons/concepts-icon.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/assets/role-icons/dapp-dev-icon.svg b/src/assets/role-icons/dapp-dev-icon.svg new file mode 100644 index 00000000000..04dd64c232d --- /dev/null +++ b/src/assets/role-icons/dapp-dev-icon.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/assets/role-icons/frontend-dev-icon.svg b/src/assets/role-icons/frontend-dev-icon.svg new file mode 100644 index 00000000000..39b17f4364f --- /dev/null +++ b/src/assets/role-icons/frontend-dev-icon.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/assets/role-icons/index.ts b/src/assets/role-icons/index.ts new file mode 100644 index 00000000000..17473a7bf2d --- /dev/null +++ b/src/assets/role-icons/index.ts @@ -0,0 +1,15 @@ +import conceptsIcon from "./concepts-icon.svg" +import dappDevIcon from "./dapp-dev-icon.svg" +import tokenDevIcon from "./token-dev-icon.svg" +import referenceIcon from "./reference-icon.svg" +import quickstartIcon from "./quickstart-icon.svg" +import frontendDevIcon from "./frontend-dev-icon.svg" + +export const roleIconMap: Record = { + concepts: conceptsIcon.src, + dappDev: dappDevIcon.src, + tokenDev: tokenDevIcon.src, + reference: referenceIcon.src, + quickstart: quickstartIcon.src, + frontendDev: frontendDevIcon.src, +} diff --git a/src/assets/role-icons/quickstart-icon.svg b/src/assets/role-icons/quickstart-icon.svg new file mode 100644 index 00000000000..6ee9745e22c --- /dev/null +++ b/src/assets/role-icons/quickstart-icon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/assets/role-icons/reference-icon.svg b/src/assets/role-icons/reference-icon.svg new file mode 100644 index 00000000000..107c33cb1f9 --- /dev/null +++ b/src/assets/role-icons/reference-icon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/assets/role-icons/token-dev-icon.svg b/src/assets/role-icons/token-dev-icon.svg new file mode 100644 index 00000000000..6a79aa8abfe --- /dev/null +++ b/src/assets/role-icons/token-dev-icon.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/CCIP/Chain/Chain.astro b/src/components/CCIP/Chain/Chain.astro index 512ff852cc7..7c5e63a0f69 100644 --- a/src/components/CCIP/Chain/Chain.astro +++ b/src/components/CCIP/Chain/Chain.astro @@ -1,6 +1,5 @@ --- import CcipLayout from "~/layouts/CcipLayout.astro" -import { getEntry } from "astro:content" import { Environment, getAllNetworkLanes, @@ -12,14 +11,28 @@ import { import ChainHero from "~/components/CCIP/ChainHero/ChainHero" import ChainTable from "~/components/CCIP/Tables/ChainTable" import { getTokenIconUrl } from "~/features/utils" -import Drawer from "../Drawer/Drawer" import ChainTokenGrid from "./ChainTokenGrid" +import { BaseFrontmatter } from "~/content/config" + +const { environment, network } = Astro.props + +// Define frontmatter and headings directly +const frontmatter: BaseFrontmatter = { + title: `CCIP Supported Networks - ${network.name}`, + section: "ccip" as const, + metadata: { + description: `View supported tokens and lanes for ${network.name} on Chainlink CCIP.`, + }, +} + +const headings = [ + { + depth: 1, + slug: "overview", + text: frontmatter.title, + }, +] -// TODO: Add type for network -const { environment, network } = Astro.props as { environment: Environment; network: any } - -const entry = await getEntry("ccip", "index") -const { headings } = await entry.render() const networks = getAllNetworks({ filter: environment }) const allTokens = getTokensOfChain({ @@ -46,14 +59,7 @@ const lanes = await getAllNetworkLanes({ const searchLanes = getSearchLanes({ environment }) --- - + { const searchLanes = getSearchLanes({ environment }) --- - +
diff --git a/src/components/CCIP/Token/Token.astro b/src/components/CCIP/Token/Token.astro index 9cde2a10ca3..daa30944c7e 100644 --- a/src/components/CCIP/Token/Token.astro +++ b/src/components/CCIP/Token/Token.astro @@ -1,6 +1,5 @@ --- import CcipLayout from "~/layouts/CcipLayout.astro" -import { getEntry } from "astro:content" import { getAllNetworks, getAllSupportedTokens, @@ -14,12 +13,25 @@ import { import ChainHero from "~/components/CCIP/ChainHero/ChainHero" import Table from "~/components/CCIP/Tables/TokenChainsTable" import { directoryToSupportedChain, getChainIcon, getExplorer, getTitle, getTokenIconUrl } from "~/features/utils" -import Drawer from "~/components/CCIP/Drawer/Drawer" const { token, logo, environment } = Astro.props as { token: string; logo: string; environment: Environment } -const entry = await getEntry("ccip", "index") -const { headings } = await entry.render() +const frontmatter = { + title: `CCIP Supported Tokens - ${token}`, + section: "ccip" as const, + metadata: { + description: `View supported blockchains and configurations for ${token} token on Chainlink CCIP.`, + }, +} + +const headings = [ + { + depth: 1, + slug: "overview", + text: frontmatter.title, + }, +] + const networks = getAllNetworks({ filter: environment }) const supportedTokens = getAllSupportedTokens({ @@ -56,14 +68,7 @@ const tokenLanes = getAllTokenLanes({ const searchLanes = getSearchLanes({ environment }) --- - + = { + ccip: ccipRoles, + // Add other products here when they're ready for the role-based layout +} diff --git a/src/config/roles/types.ts b/src/config/roles/types.ts new file mode 100644 index 00000000000..8b318a6c302 --- /dev/null +++ b/src/config/roles/types.ts @@ -0,0 +1,20 @@ +export type LinkType = "overview" | "concept" | "get-started" | "guide" | "reference" + +export interface RoleLink { + type: LinkType + title: string + url: string +} + +export interface RoleConfig { + id: string + title: string + description: string + iconType: string + links: RoleLink[] +} + +export interface ProductRoles { + productId: string + roles: RoleConfig[] +} diff --git a/src/config/sidebar.ts b/src/config/sidebar.ts index 6b0a13ed1ac..0da19585e70 100644 --- a/src/config/sidebar.ts +++ b/src/config/sidebar.ts @@ -971,6 +971,10 @@ export const SIDEBAR: Partial> = { title: "Overview", url: "ccip", }, + { + title: "About CCIP", + url: "ccip/about", + }, { title: "Getting Started", url: "ccip/getting-started", diff --git a/src/content/ccip/index.mdx b/src/content/ccip/about.mdx similarity index 96% rename from src/content/ccip/index.mdx rename to src/content/ccip/about.mdx index 65bcfb9be31..cee7d7bd56f 100644 --- a/src/content/ccip/index.mdx +++ b/src/content/ccip/about.mdx @@ -2,7 +2,8 @@ section: ccip date: Last Modified title: "Chainlink CCIP" -isIndex: true +metadata: + description: "Chainlink CCIP is a secure blockchain interoperability protocol enabling cross-chain token transfers and messaging. Built with defense-in-depth security, it powers Web3 applications across multiple networks with industry-leading oracle infrastructure." whatsnext: { "Complete the Getting Started guide to learn the basics": "/ccip/getting-started", diff --git a/src/features/landing/components/RoleCardGeneric.tsx b/src/features/landing/components/RoleCardGeneric.tsx new file mode 100644 index 00000000000..14f55c4a8fe --- /dev/null +++ b/src/features/landing/components/RoleCardGeneric.tsx @@ -0,0 +1,56 @@ +import React from "react" +import type { RoleConfig } from "@config/roles/types" +import { roleIconMap } from "@assets/role-icons" +import styles from "./RoleCards.module.css" + +interface RoleCardGenericProps { + role: RoleConfig +} + +export const RoleCardGeneric = ({ role }: RoleCardGenericProps) => { + const { title, description, iconType, links } = role + + const groupedLinks = links.reduce((acc, link) => { + if (!acc[link.type]) { + acc[link.type] = [] + } + acc[link.type].push(link) + return acc + }, {} as Record) + + return ( +
+
+
+
+ {iconType && ( + + )} +
+

+ {title} +

+
+ +

{description}

+ + +
+
+ ) +} diff --git a/src/features/landing/components/RoleCards.module.css b/src/features/landing/components/RoleCards.module.css new file mode 100644 index 00000000000..6be7e4876e4 --- /dev/null +++ b/src/features/landing/components/RoleCards.module.css @@ -0,0 +1,247 @@ +.rolesSection { + width: 100%; + max-width: var(--doc-content-width); + margin: 0 auto; + padding: var(--space-6x) var(--space-4x); +} + +.rolesContainer { + margin: 0 auto; + width: 100%; +} + +.cardGrid { + display: grid; + gap: var(--space-6x); + grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); + max-width: 1200px; + margin: 0 auto; + align-items: start; +} + +.card { + position: relative; + border-radius: 12px; + background: var(--color-background); + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + height: 480px; + border: 1px solid var(--Card-Border, #e4e8ed); + background-color: var(--color-background, #ffffff); + display: flex; +} + +.card:hover { + transform: translateY(-2px); + box-shadow: 0 8px 12px rgba(26, 43, 107, 0.1); + background: var(--Blue-Blue-50, #eff6ff); +} + +.cardInner { + padding: var(--space-6x); + display: flex; + flex-direction: column; + gap: var(--space-3x); + flex: 1; + width: 100%; + display: grid; + grid-template-rows: auto auto 1fr; +} + +.cardHeader { + display: flex; + align-items: center; + gap: var(--space-4x); +} + +.iconWrapper { + flex-shrink: 0; + padding: var(--space-2x); + border-radius: 8px; + background: var(--color-background-secondary); + transition: transform 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; +} + +.card:hover .iconWrapper { + transform: scale(1.05); +} + +.cardIcon { + width: var(--space-8x); + height: var(--space-8x); + display: flex; +} + +.cardTitle { + font-size: var(--font-size-xl); + font-weight: 600; + color: var(--color-text-heading); + margin: 0; + line-height: 1.2; + display: flex; + align-items: center; +} + +.cardDescription { + color: var(--color-text-secondary); + font-size: var(--font-size-base); + line-height: 1.6; + margin: 0; + height: 4.8em; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + line-clamp: 3; + text-overflow: ellipsis; + opacity: 0.9; +} + +.linkGroups { + display: flex; + flex-direction: column; + gap: var(--space-6x); + margin-top: var(--space-6x); + overflow-y: auto; + -webkit-overflow-scrolling: touch; + /* Customize scrollbar for modern browsers */ + scrollbar-width: thin; + scrollbar-color: var(--gray-200) transparent; + + /* Webkit scrollbar styling */ + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background-color: var(--gray-200); + border-radius: 3px; + } + + &::-webkit-scrollbar-thumb:hover { + background-color: var(--gray-300); + } + @media (prefers-reduced-motion: no-preference) { + scroll-behavior: smooth; + } +} + +.linkGroup { + display: flex; + flex-direction: column; + gap: var(--space-2x); +} + +.linkGroupTitle { + font-size: var(--font-size-sm); + font-weight: 600; + color: var(--color-text-secondary); + text-transform: capitalize; + margin: 0; + letter-spacing: 0.5px; +} + +.linkList { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: var(--space-2x); +} + +.link { + color: var(--blue-500); + text-decoration: none; + font-size: var(--font-size-base); + line-height: 1.4; + transition: all 0.2s ease; + display: inline-flex; + align-items: center; + padding: var(--space-1x) 0; + position: relative; + font-weight: 500; +} + +.linkText { + position: relative; +} + +.linkText::after { + content: ""; + position: absolute; + bottom: -2px; + left: 0; + width: 100%; + height: 1px; + background: currentColor; + transform: scaleX(0); + transform-origin: right; + transition: transform 0.3s ease; +} + +.link:hover .linkText::after, +.link:focus .linkText::after { + transform: scaleX(1); + transform-origin: left; +} + +.link:hover { + color: var(--blue-600); + text-decoration: underline; +} + +/* Responsive adjustments */ +@media (max-width: 1200px) { + .cardGrid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 768px) { + .rolesSection { + padding: var(--space-4x) var(--space-2x); + } + + .cardGrid { + grid-template-columns: 1fr; + gap: var(--space-4x); + } + + .cardInner { + padding: var(--space-4x); + } + + .cardTitle { + font-size: var(--font-size-lg); + } +} + +/* High contrast mode */ +@media (forced-colors: active) { + .card { + border: 2px solid CanvasText; + } + + .link:focus { + outline: 2px solid CanvasText; + } +} + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + .card, + .iconWrapper, + .link, + .linkText::after { + transition: none; + } +} diff --git a/src/features/landing/components/RoleCardsGeneric.module.css b/src/features/landing/components/RoleCardsGeneric.module.css new file mode 100644 index 00000000000..75dd5fd0dca --- /dev/null +++ b/src/features/landing/components/RoleCardsGeneric.module.css @@ -0,0 +1,21 @@ +.cardGrid { + display: grid; + gap: var(--space-6x); + width: 100%; +} + +/* Single column by default */ +@media (min-width: 640px) { + .cardGrid { + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: var(--space-4x); + } +} + +/* Two columns when there's enough space */ +@media (min-width: 1024px) { + .cardGrid { + grid-template-columns: repeat(2, 1fr); + gap: var(--space-6x); + } +} diff --git a/src/features/landing/components/RoleCardsGeneric.tsx b/src/features/landing/components/RoleCardsGeneric.tsx new file mode 100644 index 00000000000..c93690c144f --- /dev/null +++ b/src/features/landing/components/RoleCardsGeneric.tsx @@ -0,0 +1,21 @@ +import type { RoleConfig } from "@config/roles/types" +import { RoleCardGeneric } from "./RoleCardGeneric" +import styles from "./RoleCards.module.css" + +interface RoleCardsGenericProps { + roles: RoleConfig[] +} + +export const RoleCardsGeneric = ({ roles }: RoleCardsGenericProps) => { + return ( +
+
+
+ {roles.map((role) => ( + + ))} +
+
+
+ ) +} diff --git a/src/layouts/DocsLayout.astro b/src/layouts/DocsLayout.astro index 3d119222496..94b7e2896f5 100644 --- a/src/layouts/DocsLayout.astro +++ b/src/layouts/DocsLayout.astro @@ -15,8 +15,10 @@ import { detectApiReference } from "@components/VersionSelector/utils/versions" interface Props { frontmatter: BaseFrontmatter headings?: MarkdownHeading[] + hideRightSidebar?: boolean + hideWhatsNext?: boolean } -const { frontmatter, headings } = Astro.props +const { frontmatter, headings, hideRightSidebar, hideWhatsNext } = Astro.props const titleHeading: MarkdownHeading = { text: frontmatter.title, @@ -62,11 +64,15 @@ const { isApiReference, product, isVersioned } = detectApiReference(currentPage) - {whatsNext && } + {whatsNext && !hideWhatsNext && }
- + { + !hideRightSidebar && ( + + ) + } diff --git a/src/pages/ccip/index.astro b/src/pages/ccip/index.astro index 12f32f78397..b9395aadff8 100644 --- a/src/pages/ccip/index.astro +++ b/src/pages/ccip/index.astro @@ -1,10 +1,39 @@ --- import DocsLayout from "~/layouts/DocsLayout.astro" -import { getEntry } from "astro:content" -const entry = await getEntry("ccip", "index") -const { Content, headings } = await entry.render() +import { RoleCardsGeneric } from "~/features/landing/components/RoleCardsGeneric" +import { PRODUCT_CONFIGS } from "~/config/roles/getRolesByProduct" +import type { BaseFrontmatter } from "~/content/config" + +// Define frontmatter that was previously in index.mdx +const frontmatter: BaseFrontmatter = { + title: "Chainlink CCIP Documentation", + section: "ccip" as const, + metadata: { + description: + "Learn how to build cross-chain applications with Chainlink CCIP. Transfer tokens and/or data between blockchain networks securely and reliably.", + }, + isIndex: true, +} + +// Create headings array starting with the main title +const titleHeading = { + text: frontmatter.title, + slug: "overview", + depth: 1, +} + +const headings = [titleHeading] + +const productRoles = PRODUCT_CONFIGS.ccip --- - - + + {productRoles && } + + diff --git a/src/types/svg.d.ts b/src/types/svg.d.ts new file mode 100644 index 00000000000..aa39b450408 --- /dev/null +++ b/src/types/svg.d.ts @@ -0,0 +1,4 @@ +declare module "*.svg" { + const content: string + export default content +} diff --git a/tsconfig.json b/tsconfig.json index ef11db7bc8d..3f0b9029ae9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,7 +30,8 @@ "@utils": ["src/utils/"], "@utils/*": ["src/utils/*"], "@variables": ["src/config/markdown-variables.ts"], - "@abi": ["src/features/abi/index.ts"] + "@abi": ["src/features/abi/index.ts"], + "@assets/role-icons": ["src/assets/role-icons/index"] }, "strictNullChecks": true, "verbatimModuleSyntax": false