Skip to content

Commit 805cb62

Browse files
authored
(feat) Reusable ChainSelector component (#3032)
* chain selector basic * cleanup * mainnet table/testnet table conditional * various fixes * more cleanup * hydration mismatch fix: prevent default feed flashing when arriving via url param * lint fix * removed unused imports * fixed default network checking * styling update * feedback & mobile styling fixes * increase size of network switch button * lint fix + chain->selectedChain * cleanup * keyboard controls and focus state * more fixes * fixed tab selection for dropdown elements * url params for preserving user election in url params * more switching logic edge cases (mainnet+testnet, mainnet only, testnet only -- switching between all of these with expected behavior * added ChainSelector.example.tsx * fix cl-search-frontend in packagelock breaking ci * fixed network toggle to be easier to click on mobile * log removal
1 parent db2380f commit 805cb62

File tree

5 files changed

+1141
-245
lines changed

5 files changed

+1141
-245
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/** @jsxImportSource preact */
2+
import { useState } from "preact/hooks"
3+
import { ChainSelector } from "./ChainSelector.tsx"
4+
import type { Chain } from "~/features/data/chains.ts"
5+
6+
/**
7+
* ChainSelectorExample - Minimal usage example for the ChainSelector component
8+
*/
9+
export function ChainSelectorExample() {
10+
// Sample chain data - demonstrates different network availability scenarios
11+
const sampleChains: Chain[] = [
12+
{
13+
page: "ethereum",
14+
title: "Ethereum",
15+
label: "Ethereum",
16+
img: "/assets/chains/ethereum.svg",
17+
networkStatusUrl: "https://status.chain.link/",
18+
supportedFeatures: ["feeds"],
19+
tags: ["default"],
20+
networks: [
21+
{
22+
name: "Ethereum Mainnet",
23+
explorerUrl: "https://etherscan.io/",
24+
networkType: "mainnet",
25+
queryString: "ethereum-mainnet",
26+
},
27+
{
28+
name: "Ethereum Sepolia",
29+
explorerUrl: "https://sepolia.etherscan.io/",
30+
networkType: "testnet",
31+
queryString: "ethereum-testnet-sepolia",
32+
},
33+
],
34+
},
35+
{
36+
page: "polygon",
37+
title: "Polygon (Mainnet Only)",
38+
label: "Polygon",
39+
img: "/assets/chains/polygon.svg",
40+
networkStatusUrl: "https://status.chain.link/",
41+
supportedFeatures: ["feeds"],
42+
tags: ["default"],
43+
networks: [
44+
{
45+
name: "Polygon Mainnet",
46+
explorerUrl: "https://polygonscan.com/",
47+
networkType: "mainnet",
48+
queryString: "polygon-mainnet",
49+
},
50+
],
51+
},
52+
{
53+
page: "base",
54+
title: "Base (Testnet Only)",
55+
label: "Base",
56+
img: "/assets/chains/base.svg",
57+
networkStatusUrl: "https://status.chain.link/",
58+
supportedFeatures: ["feeds"],
59+
tags: ["default"],
60+
networks: [
61+
{
62+
name: "Base Sepolia",
63+
explorerUrl: "https://sepolia.basescan.org/",
64+
networkType: "testnet",
65+
queryString: "base-testnet-sepolia",
66+
},
67+
],
68+
},
69+
]
70+
71+
// Component state
72+
const [selectedChain, setSelectedChain] = useState<Chain>(sampleChains[0])
73+
const [selectedNetworkType, setSelectedNetworkType] = useState<"mainnet" | "testnet">("mainnet")
74+
75+
// Event handlers
76+
const handleChainSelect = (chain: Chain) => {
77+
setSelectedChain(chain)
78+
79+
// Auto-correct network type if not available on new chain
80+
const newAvailableTypes = {
81+
mainnet: chain.networks?.some((network) => network.networkType === "mainnet") ?? false,
82+
testnet: chain.networks?.some((network) => network.networkType === "testnet") ?? false,
83+
}
84+
85+
if (selectedNetworkType === "mainnet" && !newAvailableTypes.mainnet && newAvailableTypes.testnet) {
86+
setSelectedNetworkType("testnet")
87+
} else if (selectedNetworkType === "testnet" && !newAvailableTypes.testnet && newAvailableTypes.mainnet) {
88+
setSelectedNetworkType("mainnet")
89+
}
90+
91+
console.log("Chain selected:", chain.title)
92+
}
93+
94+
const handleNetworkTypeChange = (networkType: "mainnet" | "testnet") => {
95+
setSelectedNetworkType(networkType)
96+
console.log("Network type changed:", networkType)
97+
}
98+
99+
// Calculate available network types based on the selected chain's actual networks
100+
const availableNetworkTypes = {
101+
mainnet: selectedChain.networks?.some((network) => network.networkType === "mainnet") ?? false,
102+
testnet: selectedChain.networks?.some((network) => network.networkType === "testnet") ?? false,
103+
}
104+
105+
return (
106+
<div style={{ padding: "20px", maxWidth: "400px" }}>
107+
<h2>ChainSelector Example</h2>
108+
109+
<ChainSelector
110+
chains={sampleChains}
111+
selectedChain={selectedChain}
112+
onChainSelect={handleChainSelect}
113+
onNetworkTypeChange={handleNetworkTypeChange}
114+
selectedNetworkType={selectedNetworkType}
115+
availableNetworkTypes={availableNetworkTypes}
116+
dataFeedType="default"
117+
/>
118+
119+
<div style={{ marginTop: "20px", fontSize: "14px" }}>
120+
<p>
121+
<strong>Selected:</strong> {selectedChain.title}
122+
</p>
123+
<p>
124+
<strong>Network Type:</strong> {selectedNetworkType}
125+
</p>
126+
<p>
127+
<strong>Available:</strong>
128+
</p>
129+
<ul style={{ margin: "5px 0", paddingLeft: "20px" }}>
130+
<li>Mainnet: {availableNetworkTypes.mainnet ? "✅" : "❌"}</li>
131+
<li>Testnet: {availableNetworkTypes.testnet ? "✅" : "❌"}</li>
132+
</ul>
133+
</div>
134+
</div>
135+
)
136+
}

0 commit comments

Comments
 (0)