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
21 changes: 21 additions & 0 deletions src/components/CCIP/AptosCCTCallout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/** @jsxImportSource react */
import { useStore } from "@nanostores/react"
import { selectedChainType } from "~/stores/chainType.js"
import { Callout } from "@components/Callout/Callout.tsx"

export function AptosCCTCallout() {
const chainType = useStore(selectedChainType)

if (chainType !== "aptos") {
return null
}

return (
<Callout type="caution" title="Aptos CCT Documentation Coming Soon">
Cross-Chain Token (CCT) implementation documentation for Aptos is currently in development. If you are a token
developer and wish to enable your token on Aptos, please submit your project details via this{" "}
<a href="https://chain.link/ccip-contact">contact form</a>. When filling out the form, select{" "}
<strong>Token admin registration</strong> as the support request type.
</Callout>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { TutorialStep } from "../TutorialSetup/TutorialStep.tsx"
import { NetworkAddress } from "./NetworkAddress.tsx"
import { StepCheckbox } from "../TutorialProgress/StepCheckbox.tsx"
import { SolidityParam } from "../TutorialSetup/SolidityParam.tsx"
import { Callout } from "../TutorialSetup/Callout.tsx"
import { Callout } from "@components/Callout/Callout.tsx"
import styles from "./AdminSetupStep.module.css"

interface AdminSetupStepProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { laneStore, type RateLimiterConfig, updateRateLimits } from "~/stores/la
import { useStore } from "@nanostores/react"
import styles from "./ChainUpdateBuilder.module.css"
import { ErrorBoundary } from "~/components/ErrorBoundary.tsx"
import { Callout } from "../TutorialSetup/Callout.tsx"
import { Callout } from "@components/Callout/Callout.tsx"

interface ChainUpdateBuilderProps {
chain: "source" | "destination"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TutorialStep } from "../TutorialSetup/TutorialStep.tsx"
import { Callout } from "../TutorialSetup/Callout.tsx"
import { Callout } from "@components/Callout/Callout.tsx"
import type { Network } from "~/config/data/ccip/types.ts"
import styles from "./ContractVerificationStep.module.css"
import { getExplorerAddressUrl } from "~/features/utils/index.ts"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { SolidityParam } from "../TutorialSetup/SolidityParam.tsx"
import { StoredContractAddress } from "./StoredContractAddress.tsx"
import { NetworkAddress } from "./NetworkAddress.tsx"
import { ContractVerificationStep } from "./ContractVerificationStep.tsx"
import { Callout } from "../TutorialSetup/Callout.tsx"
import { Callout } from "@components/Callout/Callout.tsx"
import type { LaneState, DeployedContracts } from "~/stores/lanes/index.ts"
import styles from "./DeployPoolStep.module.css"
import { isAddress } from "ethers"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { TutorialCard } from "../TutorialSetup/TutorialCard.tsx"
import { TutorialStep } from "../TutorialSetup/TutorialStep.tsx"
import { NetworkCheck } from "../TutorialSetup/NetworkCheck.tsx"
import { SolidityParam } from "../TutorialSetup/SolidityParam.tsx"
import { Callout } from "../TutorialSetup/Callout.tsx"
import { Callout } from "@components/Callout/Callout.tsx"
import { ContractVerificationStep } from "./ContractVerificationStep.tsx"
import type { LaneState, DeployedContracts } from "~/stores/lanes/index.ts"
import styles from "./DeployTokenStep.module.css"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { NetworkCheck } from "../TutorialSetup/NetworkCheck.tsx"
import { StepCheckbox } from "../TutorialProgress/StepCheckbox.tsx"
import { SolidityParam } from "../TutorialSetup/SolidityParam.tsx"
import { StoredContractAddress } from "./StoredContractAddress.tsx"
import { Callout } from "../TutorialSetup/Callout.tsx"
import { Callout } from "@components/Callout/Callout.tsx"
import { TutorialCard } from "../TutorialSetup/TutorialCard.tsx"
import styles from "./GrantPrivilegesStep.module.css"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import styles from "./PoolConfigVerification.module.css"
import { TutorialCard, TutorialStep } from "../TutorialSetup/index.ts"
import { NetworkCheck } from "../TutorialSetup/NetworkCheck.tsx"
import { SolidityParam } from "../TutorialSetup/SolidityParam.tsx"
import { Callout } from "../TutorialSetup/Callout.tsx"
import { Callout } from "@components/Callout/Callout.tsx"
import { StepCheckbox } from "../TutorialProgress/StepCheckbox.tsx"

type ChainType = "source" | "destination"
Expand Down
147 changes: 147 additions & 0 deletions src/components/ChainSelector/ChainTypeSelector.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
.selector {
display: flex;
align-items: center;
padding: var(--space-3x) var(--space-3x);
border-bottom: 1px solid var(--border-color, #e5e7eb);
background: var(--color-background-primary, #ffffff);
position: sticky;
top: 0;
z-index: 10;
}

.dropdown {
position: relative;
width: auto;
min-width: 160px;
}

.dropdownButton {
display: flex;
align-items: center;
gap: var(--space-2x);
width: 100%;
padding: var(--space-2x) var(--space-3x);
border: 1.5px solid var(--border-color, #e5e7eb);
border-radius: 6px;
background: var(--color-background-primary, #ffffff);
color: var(--color-text-primary);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}

.dropdownButton:hover {
background: var(--color-background-secondary, #f9fafb);
border-color: var(--color-text-tertiary, #9ca3af);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
transform: translateY(-1px);
}

.dropdownButton:active {
transform: translateY(0);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}

.dropdownButton:focus-visible {
outline: 2px solid var(--color-blue-600, #2563eb);
outline-offset: 2px;
border-color: var(--color-blue-600, #2563eb);
}

.arrow {
margin-left: auto;
color: var(--color-text-secondary, #6b7280);
transition: transform 0.2s ease;
}

.arrowOpen {
transform: rotate(180deg);
}

.dropdownMenu {
position: absolute;
top: calc(100% + 4px);
left: 0;
right: 0;
margin: 0;
padding: var(--space-2x) 0;
list-style: none;
background: var(--color-background-primary, #ffffff);
border: 1.5px solid var(--border-color, #e5e7eb);
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 100;
max-height: 300px;
overflow-y: auto;
}

.dropdownMenu li {
margin: 0;
padding: 0;
}

.dropdownItem {
display: flex;
align-items: center;
gap: var(--space-2x);
width: 100%;
padding: var(--space-2x) var(--space-3x);
border: none;
background: transparent;
color: var(--color-text-primary);
font-size: 14px;
font-weight: 500;
text-align: left;
cursor: pointer;
transition: all 0.15s ease;
}

.dropdownItem:hover {
background: var(--color-background-secondary, #f9fafb);
padding-left: calc(var(--space-3x) + 2px);
}

.dropdownItem:focus-visible {
outline: 2px solid var(--chain-color);
outline-offset: -2px;
background: var(--color-background-secondary, #f9fafb);
}

.dropdownItemActive {
color: var(--color-blue-600, #2563eb);
font-weight: 600;
background: var(--color-blue-50, #eff6ff);
}

.icon {
width: 20px;
height: 20px;
flex-shrink: 0;
object-fit: contain;
}

.name {
white-space: nowrap;
flex: 1;
}

.checkmark {
margin-left: auto;
color: var(--color-blue-600, #2563eb);
flex-shrink: 0;
}

@media (max-width: 640px) {
.dropdown {
width: 100%;
min-width: 0;
}
}

@media (max-width: 480px) {
.dropdownMenu {
max-height: 240px;
}
}
154 changes: 154 additions & 0 deletions src/components/ChainSelector/ChainTypeSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/** @jsxImportSource react */
import { useStore } from "@nanostores/react"
import { useState, useRef, useEffect } from "react"
import { selectedChainType, setChainType } from "~/stores/chainType.js"
import { CHAIN_TYPE_CONFIGS, CCIP_SUPPORTED_CHAINS } from "~/config/chainTypes.js"
import { CCIP_SIDEBAR_CONTENT } from "~/config/sidebar/ccip-dynamic.js"
import { findEquivalentPageUrlWithFallback } from "~/utils/chainNavigation.js"
import type { ChainType } from "~/config/types.js"
import styles from "./ChainTypeSelector.module.css"

/**
* Chain Type Dropdown Selector Component
*
* Allows users to filter CCIP documentation by blockchain type (EVM, Solana, Aptos).
* Uses a dropdown design that scales well with many chain types.
*
* Features:
* - Scalable dropdown design (works with 10+ chains)
* - Smart navigation: automatically switches to equivalent page for selected chain
* - Sidebar-driven: uses sidebar config as source of truth for navigation
* - Reactive UI based on nanostore state
* - Google Analytics tracking
* - Keyboard accessible (ARIA compliant)
* - Click-outside to close
* - Mobile responsive
*
* Behavior:
* - If equivalent page exists for target chain → Navigate to it
* - If no equivalent page → Update filter state only (sidebar filters content)
*/
export function ChainTypeSelector() {
const activeChain = useStore(selectedChainType)
const [isOpen, setIsOpen] = useState(false)
const dropdownRef = useRef<HTMLDivElement>(null)

const activeConfig = CHAIN_TYPE_CONFIGS[activeChain]

const handleSelect = (chainType: ChainType) => {
setIsOpen(false)
setChainType(chainType)

// Find target URL with intelligent fallback: exact match → parent → section root
const targetUrl = findEquivalentPageUrlWithFallback(window.location.pathname, chainType, CCIP_SIDEBAR_CONTENT)

window.location.href = `/${targetUrl}`
}

const handleToggle = () => {
setIsOpen(!isOpen)
}

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false)
}
}

const handleEscape = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setIsOpen(false)
}
}

if (isOpen) {
document.addEventListener("mousedown", handleClickOutside)
document.addEventListener("keydown", handleEscape)
}

return () => {
document.removeEventListener("mousedown", handleClickOutside)
document.removeEventListener("keydown", handleEscape)
}
}, [isOpen])

return (
<div className={styles.selector} ref={dropdownRef}>
<div className={styles.dropdown}>
<button
type="button"
className={styles.dropdownButton}
onClick={handleToggle}
aria-expanded={isOpen}
aria-haspopup="listbox"
aria-label="Select blockchain type"
style={{ "--chain-color": activeConfig.color } as React.CSSProperties}
>
<img src={activeConfig.icon} alt="" className={styles.icon} width="20" height="20" aria-hidden="true" />
<span className={styles.name}>{activeConfig.displayName}</span>
<svg
className={`${styles.arrow} ${isOpen ? styles.arrowOpen : ""}`}
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M2.5 4.5L6 8L9.5 4.5"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>

{isOpen && (
<ul className={styles.dropdownMenu} role="listbox" aria-label="Blockchain options">
{CCIP_SUPPORTED_CHAINS.map((chainId) => {
const config = CHAIN_TYPE_CONFIGS[chainId]
const isActive = activeChain === chainId

return (
<li key={chainId} role="option" aria-selected={isActive}>
<button
type="button"
className={`${styles.dropdownItem} ${isActive ? styles.dropdownItemActive : ""}`}
onClick={() => handleSelect(chainId)}
style={{ "--chain-color": config.color } as React.CSSProperties}
title={config.description}
>
<img src={config.icon} alt="" className={styles.icon} width="20" height="20" aria-hidden="true" />
<span className={styles.name}>{config.displayName}</span>
{isActive && (
<svg
className={styles.checkmark}
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M13.5 4.5L6 12L2.5 8.5"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
</button>
</li>
)
})}
</ul>
)}
</div>
</div>
)
}
2 changes: 1 addition & 1 deletion src/components/ChainSelector/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { ChainSelector } from "./ChainSelector.tsx"
export { ChainTypeSelector } from "./ChainTypeSelector.js"
Loading
Loading