Skip to content

[FRE-1668] Update web-component to allow passing props in the same format as the react component (camelCase) #1055

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Apr 15, 2025
5 changes: 5 additions & 0 deletions .changeset/strange-taxis-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@skip-go/widget": patch
---

Update web-component to allow passing props via javascript properties
24 changes: 14 additions & 10 deletions docs/widget/web-component.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,24 @@ This can be added to npm scripts in `package.json`, a `.env file`, or used when

## Usage

Props are the same as [`WidgetProps`](./configuration), but passed as attributes in kebab-case. Use strings or stringified objects for complex props.
Props are the exact same as [`WidgetProps`](./configuration) but you are required to pass them to the element via javascript/typescript.

```tsx
<div style="width:100%; max-width:500px; padding:0 10px;">
<skip-widget
theme='{
"brandColor": "#FF4FFF",
}'
default-route='{
"srcChainId": "osmosis-1",
"srcAssetDenom": "ibc/1480b8fd20ad5fcae81ea87584d269547dd4d436843c1d20f15e00eb64743ef4"
}'
></skip-widget>
<skip-widget></skip-widget>
</div>
<script>
const skipWidget = document.querySelector("skip-widget");
if (skipWidget) {
skipWidget.theme = {
brandColor: "#FF4FFF",
};
skipWidget.defaultRoute = {
srcChainId: "osmosis-1",
srcAssetDenom: "ibc/1480b8fd20ad5fcae81ea87584d269547dd4d436843c1d20f15e00eb64743ef4",
}
}
</script>
Comment on lines 33 to +53
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can add this to the documentation, I'm not sure there is a doc for web component.

@codingki docs are here and this is where it shows up https://docs.skip.build/go/widget/web-component

```

## Performance Considerations
Expand Down
181 changes: 181 additions & 0 deletions examples/nextjs/src/app/web-component/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
'use client';

import React, { useState } from "react";
import { Widget } from "@skip-go/widget";
import { PhantomWalletAdapter } from "@solana/wallet-adapter-phantom";
import { createWalletClient, custom, Account } from "viem";
import { mainnet, optimism, polygon, base, arbitrum, avalanche } from 'viem/chains';

type ChainId = string;
type Address = string;

export default function Home() {
// This state holds a mapping from chain IDs to connected addresses.
const [connectedAddresses, setConnectedAddresses] = useState<Record<ChainId, Address>>();

/**
* Helper to update the connectedAddresses with a given chainId and address.
*/
const updateAccount = (chainId: ChainId, address: Address) => {
setConnectedAddresses((prev) => ({
...prev,
[chainId]: address,
}));
};

/**
* Connect to an EVM-compatible wallet (e.g., MetaMask).
*/
const connectEthereum = async () => {
const ethereum = window.ethereum;
if (!ethereum) {
throw new Error("MetaMask not installed");
}

// Request accounts
const accounts = (await ethereum.request({
method: "eth_requestAccounts",
})) as string[];

const evmAddress = accounts[0];
if (!evmAddress) throw new Error("No EVM accounts found");

// Get currently selected chain ID from MetaMask
const chainIdHex = (await ethereum.request({ method: 'eth_chainId' })) as string;
const chainId = parseInt(chainIdHex, 16).toString();

updateAccount(chainId, evmAddress);
};

/**
* Connect to a Solana wallet using Phantom Wallet Adapter.
*/
const connectSolana = async () => {
const phantom = new PhantomWalletAdapter();
await phantom.connect();
const publicKey = phantom.publicKey?.toBase58();
if (!publicKey) throw new Error("No public key found");
updateAccount("solana", publicKey);
};

/**
* Connect to Cosmos-based chains using Keplr.
*/
const connectCosmos = async () => {
const chainIds = ["cosmoshub-4", "osmosis-1"];

// Request access to the specified Cosmos chains from Keplr
await window.keplr?.enable(chainIds);

// Fetch and store addresses for each chain
await Promise.all(
chainIds.map(async (chainId) => {
const keyInfo = await window.keplr?.getKey(chainId);
if (keyInfo && keyInfo.bech32Address) {
updateAccount(chainId, keyInfo.bech32Address);
}
})
);
};

/**
* Get an offline signer for a given Cosmos chain using Keplr.
*/
const getCosmosSigner = async (chainId: string) => {
if (window.keplr?.getOfflineSigner === undefined) {
throw new Error("Keplr extension not installed");
}
const offlineSigner = await window.keplr?.getOfflineSigner(chainId);
return offlineSigner;
}

/**
* Get an EVM-compatible signer by creating a viem wallet client.
*/
const chainConfigMap: Record<string, any> = {
"1": mainnet,
"10": optimism,
"137": polygon,
"8453": base,
"42161": arbitrum,
"43114": avalanche,
};

const getEVMSigner = async () => {
const ethereum = window.ethereum;
if (!ethereum) {
throw new Error("MetaMask not installed");
}

// Request accounts
const accounts = (await ethereum.request({
method: "eth_requestAccounts",
})) as Account[];

const evmAddress = accounts?.[0];
if (!evmAddress) {
throw new Error("No EVM accounts found");
}

// Get the currently selected chain ID
const chainIdHex = (await ethereum.request({ method: 'eth_chainId' })) as string;
const chainId = parseInt(chainIdHex, 16).toString();

const selectedChain = chainConfigMap[chainId] ?? mainnet;

const client = createWalletClient({
account: evmAddress,
chain: selectedChain,
transport: custom(ethereum),
});

return client;
};

/**
* Get an SVM-compatible signer using Phantom.
*/
const getSVMSigner = async () => {
const phantom = new PhantomWalletAdapter();
await phantom.connect();
return phantom;
};

return (
<div
style={{
width: "100%",
maxWidth: 500,
padding: "0 10px",
boxSizing: "border-box",
}}
>
<p>Connected addresses:</p>
<ul>
{Object.entries(connectedAddresses ?? {}).map(([chainId, address]) => (
<li key={chainId}>
{chainId}: {address}
</li>
))}
</ul>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "10px",
}}
>
<button onClick={connectCosmos}>Connect Cosmos</button>
<button onClick={connectEthereum}>Connect Ethereum</button>
<button onClick={connectSolana}>Connect Solana</button>
</div>
<Widget
// Provide the connected addresses and signer retrieval functions to the Widget
connectedAddresses={connectedAddresses}
getCosmosSigner={getCosmosSigner}
getEVMSigner={getEVMSigner}
getSVMSigner={getSVMSigner}
/>
</div>
);
}
24 changes: 11 additions & 13 deletions examples/nuxtjs/app.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
<template>
<div>
<div style="width:100%; max-width:500px; padding: 0 10px;">
<skip-widget
theme='{
"backgroundColor": "#191A1C",
"textColor": "#E6EAE9",
"borderColor": "#363B3F",
"brandColor": "#FF4FFF",
"highlightColor": "#1F2022"
}'
default-route='{
"srcChainID": "osmosis-1",
"srcAssetDenom": "ibc/1480b8fd20ad5fcae81ea87584d269547dd4d436843c1d20f15e00eb64743ef4"
}'>
</skip-widget>
<skip-widget></skip-widget>
</div>
</div>
</template>

<script setup>
const skipWidget = document.querySelector("skip-widget");

if (skipWidget) {
skipWidget.onRouteUpdated = (route) => {
console.log("route updated", route);
};
}
</script>
20 changes: 11 additions & 9 deletions examples/raw-html.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@
type="module"
></script>
<div style="width: 100%; max-width: 500px; padding: 0 10px">
<skip-widget
theme='{
"backgroundColor": "#191A1C",
"textColor": "#E6EAE9",
"borderColor": "#363B3F",
"brandColor": "#FF4FFF",
"highlightColor": "#1F2022"
}'
></skip-widget>
<skip-widget></skip-widget>
</div>
</body>
</html>

<script>
const skipWidget = document.querySelector("skip-widget");

if (skipWidget) {
skipWidget.onRouteUpdated = (route) => {
console.log("route updated", route);
};
}
</script>
Loading