Skip to content

Commit 4df8070

Browse files
authored
Merge pull request #1055 from skip-mev/wip-fixing-web-component
[FRE-1668] Update web-component to allow passing props in the same format as the react component (camelCase)
2 parents 0296b1f + 13e4c5b commit 4df8070

File tree

7 files changed

+159
-133
lines changed

7 files changed

+159
-133
lines changed

.changeset/strange-taxis-talk.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@skip-go/widget": patch
3+
---
4+
5+
Update web-component to allow passing props via javascript properties

docs/widget/web-component.mdx

+14-10
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,24 @@ This can be added to npm scripts in `package.json`, a `.env file`, or used when
3333

3434
## Usage
3535

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

3838
```tsx
3939
<div style="width:100%; max-width:500px; padding:0 10px;">
40-
<skip-widget
41-
theme='{
42-
"brandColor": "#FF4FFF",
43-
}'
44-
default-route='{
45-
"srcChainId": "osmosis-1",
46-
"srcAssetDenom": "ibc/1480b8fd20ad5fcae81ea87584d269547dd4d436843c1d20f15e00eb64743ef4"
47-
}'
48-
></skip-widget>
40+
<skip-widget></skip-widget>
4941
</div>
42+
<script>
43+
const skipWidget = document.querySelector("skip-widget");
44+
if (skipWidget) {
45+
skipWidget.theme = {
46+
brandColor: "#FF4FFF",
47+
};
48+
skipWidget.defaultRoute = {
49+
srcChainId: "osmosis-1",
50+
srcAssetDenom: "ibc/1480b8fd20ad5fcae81ea87584d269547dd4d436843c1d20f15e00eb64743ef4",
51+
}
52+
}
53+
</script>
5054
```
5155

5256
## Performance Considerations

examples/nuxtjs/app.vue

+11-13
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
<template>
22
<div>
33
<div style="width:100%; max-width:500px; padding: 0 10px;">
4-
<skip-widget
5-
theme='{
6-
"backgroundColor": "#191A1C",
7-
"textColor": "#E6EAE9",
8-
"borderColor": "#363B3F",
9-
"brandColor": "#FF4FFF",
10-
"highlightColor": "#1F2022"
11-
}'
12-
default-route='{
13-
"srcChainID": "osmosis-1",
14-
"srcAssetDenom": "ibc/1480b8fd20ad5fcae81ea87584d269547dd4d436843c1d20f15e00eb64743ef4"
15-
}'>
16-
</skip-widget>
4+
<skip-widget></skip-widget>
175
</div>
186
</div>
197
</template>
8+
9+
<script setup>
10+
const skipWidget = document.querySelector("skip-widget");
11+
12+
if (skipWidget) {
13+
skipWidget.onRouteUpdated = (route) => {
14+
console.log("route updated", route);
15+
};
16+
}
17+
</script>

examples/raw-html.html

+11-9
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@
77
type="module"
88
></script>
99
<div style="width: 100%; max-width: 500px; padding: 0 10px">
10-
<skip-widget
11-
theme='{
12-
"backgroundColor": "#191A1C",
13-
"textColor": "#E6EAE9",
14-
"borderColor": "#363B3F",
15-
"brandColor": "#FF4FFF",
16-
"highlightColor": "#1F2022"
17-
}'
18-
></skip-widget>
10+
<skip-widget></skip-widget>
1911
</div>
2012
</body>
2113
</html>
14+
15+
<script>
16+
const skipWidget = document.querySelector("skip-widget");
17+
18+
if (skipWidget) {
19+
skipWidget.onRouteUpdated = (route) => {
20+
console.log("route updated", route);
21+
};
22+
}
23+
</script>

packages/widget/src/devMode/loadWidget.tsx

+68-52
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
import { StrictMode, useState } from "react";
1+
import { StrictMode, useEffect, useMemo, useState } from "react";
22
import { createRoot } from "react-dom/client";
3-
import { Widget } from "@/widget/Widget";
3+
import "../web-component";
44
import { Column, Row } from "@/components/Layout";
55
import "./global.css";
6-
import { defaultTheme, lightTheme } from "@/widget/theme";
76
import { resetWidget } from "@/state/swapPage";
7+
import { defaultTheme, lightTheme } from "@/widget/theme";
8+
import { Widget, WidgetProps } from "@/widget/Widget";
89

910
const DevMode = () => {
1011
const [theme, setTheme] = useState<"dark" | "light">("dark");
1112
const [apiUrl, setApiUrl] = useState<"prod" | "dev">("prod");
1213
const [testnet, setTestnet] = useState<boolean>(false);
1314
const [disableShadowDom, setDisableShadowDom] = useState(true);
15+
const [renderWebComponent, setRenderWebComponent] = useState(false);
1416

1517
const toggleTheme = () => {
1618
if (theme === "dark") {
@@ -20,6 +22,65 @@ const DevMode = () => {
2022
}
2123
};
2224

25+
const widgetProps: WidgetProps = useMemo(() => {
26+
return {
27+
theme: {
28+
...(theme === "dark" ? defaultTheme : lightTheme),
29+
brandTextColor: "black",
30+
brandColor: "#FF66FF",
31+
},
32+
settings: {
33+
useUnlimitedApproval: true,
34+
},
35+
disableShadowDom,
36+
onlyTestnet: testnet,
37+
routeConfig: {
38+
experimentalFeatures: ["eureka"],
39+
},
40+
apiUrl:
41+
apiUrl === "prod" ? "https://go.skip.build/api/skip" : "https://dev.go.skip.build/api/skip",
42+
ibcEurekaHighlightedAssets: {
43+
USDC: ["cosmoshub-4"],
44+
USDT: undefined,
45+
},
46+
assetSymbolsSortedToTop: [
47+
"LBTC",
48+
"ATOM",
49+
"USDC",
50+
"USDT",
51+
"ETH",
52+
"TIA",
53+
"OSMO",
54+
"NTRN",
55+
"INJ",
56+
],
57+
filterOut: {
58+
destination: {
59+
"pacific-1": ["ibc/6C00E4AA0CC7618370F81F7378638AE6C48EFF8C9203CE1C2357012B440EBDB7"],
60+
"1329": ["0xB75D0B03c06A926e488e2659DF1A861F860bD3d1"],
61+
"1": ["0xbf45a5029d081333407cc52a84be5ed40e181c46"],
62+
},
63+
},
64+
filterOutUnlessUserHasBalance: {
65+
source: {
66+
"1": ["0xbf45a5029d081333407cc52a84be5ed40e181c46"],
67+
},
68+
},
69+
};
70+
}, [apiUrl, disableShadowDom, testnet, theme]);
71+
72+
useEffect(() => {
73+
const skipWidget = document.querySelector("skip-widget");
74+
if (skipWidget) {
75+
Object.entries(widgetProps).forEach(([key, value]) => {
76+
// @ts-expect-error this is like the equivalent of
77+
// spreading the props to the web-component
78+
// but we dont expect users to do it like this
79+
skipWidget[key as keyof WidgetProps] = value;
80+
});
81+
}
82+
}, [widgetProps]);
83+
2384
return (
2485
<Column align="flex-end">
2586
<Column gap={5} style={{ width: 200 }}>
@@ -35,6 +96,9 @@ const DevMode = () => {
3596
<button onClick={() => setApiUrl((v) => (v === "prod" ? "dev" : "prod"))}>
3697
{apiUrl === "prod" ? "prod" : "dev"}
3798
</button>
99+
<button onClick={() => setRenderWebComponent((v) => !v)}>
100+
web-component: {renderWebComponent.toString()}
101+
</button>
38102
</Column>
39103
<Row
40104
style={{
@@ -55,55 +119,7 @@ const DevMode = () => {
55119
padding: "0 10px",
56120
}}
57121
>
58-
<Widget
59-
theme={{
60-
...(theme === "dark" ? defaultTheme : lightTheme),
61-
brandTextColor: "black",
62-
brandColor: "#FF66FF",
63-
}}
64-
settings={{
65-
useUnlimitedApproval: true,
66-
}}
67-
disableShadowDom={disableShadowDom}
68-
onlyTestnet={testnet}
69-
routeConfig={{
70-
experimentalFeatures: ["eureka"],
71-
}}
72-
apiUrl={
73-
apiUrl === "prod"
74-
? "https://go.skip.build/api/skip"
75-
: "https://dev.go.skip.build/api/skip"
76-
}
77-
ibcEurekaHighlightedAssets={{
78-
USDC: ["cosmoshub-4"],
79-
USDT: undefined,
80-
}}
81-
assetSymbolsSortedToTop={[
82-
"LBTC",
83-
"ATOM",
84-
"USDC",
85-
"USDT",
86-
"ETH",
87-
"TIA",
88-
"OSMO",
89-
"NTRN",
90-
"INJ",
91-
]}
92-
filterOut={{
93-
destination: {
94-
"pacific-1": [
95-
"ibc/6C00E4AA0CC7618370F81F7378638AE6C48EFF8C9203CE1C2357012B440EBDB7",
96-
],
97-
"1329": ["0xB75D0B03c06A926e488e2659DF1A861F860bD3d1"],
98-
"1": ["0xbf45a5029d081333407cc52a84be5ed40e181c46"],
99-
},
100-
}}
101-
filterOutUnlessUserHasBalance={{
102-
source: {
103-
"1": ["0xbf45a5029d081333407cc52a84be5ed40e181c46"],
104-
},
105-
}}
106-
/>
122+
{renderWebComponent ? <skip-widget /> : <Widget {...widgetProps} />}
107123
</div>
108124
</Row>
109125
</Column>

packages/widget/src/web-component.tsx

+49-48
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,74 @@
11
/* eslint-disable @typescript-eslint/consistent-type-definitions */
2-
/* eslint-disable @typescript-eslint/no-namespace */
2+
33
import toWebComponent from "@r2wc/react-to-web-component";
4-
import { Widget } from "./widget/Widget";
4+
import { NewSkipClientOptions, Widget, WidgetProps } from "./widget/Widget";
55

6-
type WebComponentProps = {
7-
container: {
8-
attributes: Record<string, string>[];
9-
};
10-
};
6+
const WEB_COMPONENT_NAME = "skip-widget";
117

12-
function isJsonString(str: string) {
13-
try {
14-
JSON.parse(str);
15-
} catch (_err) {
16-
return false;
17-
}
18-
return true;
19-
}
8+
type WebComponentProps = WidgetProps & NewSkipClientOptions;
209

21-
const camelize = (inputString: string) => {
22-
inputString = inputString.toLowerCase();
23-
return inputString.replace(/-./g, (x) => x[1].toUpperCase());
10+
type PropDescriptors = {
11+
[K in keyof WebComponentProps]: "any";
2412
};
2513

26-
const WidgetWithProvider = (props: WebComponentProps) => {
27-
const parsedProps = Array.from(props.container.attributes).map(({ name, value }) => {
28-
return { key: name, value };
29-
});
30-
31-
const realProps = parsedProps.reduce(
32-
(accumulator, initialValue) => {
33-
const { key, value } = initialValue;
34-
35-
accumulator[camelize(key)] = isJsonString(value) ? JSON.parse(value) : value;
36-
return accumulator;
37-
},
38-
{} as Record<string, string>,
39-
);
40-
41-
return <Widget {...realProps} />;
14+
const widgetPropTypes: Required<PropDescriptors> = {
15+
rootId: "any",
16+
theme: "any",
17+
brandColor: "any",
18+
onlyTestnet: "any",
19+
defaultRoute: "any",
20+
settings: "any",
21+
routeConfig: "any",
22+
filter: "any",
23+
filterOut: "any",
24+
filterOutUnlessUserHasBalance: "any",
25+
walletConnect: "any",
26+
enableSentrySessionReplays: "any",
27+
enableAmplitudeAnalytics: "any",
28+
connectedAddresses: "any",
29+
simulate: "any",
30+
disableShadowDom: "any",
31+
ibcEurekaHighlightedAssets: "any",
32+
assetSymbolsSortedToTop: "any",
33+
hideAssetsUnlessWalletTypeConnected: "any",
34+
apiUrl: "any",
35+
apiKey: "any",
36+
endpointOptions: "any",
37+
aminoTypes: "any",
38+
registryTypes: "any",
39+
chainIdsToAffiliates: "any",
40+
cacheDurationMs: "any",
41+
getCosmosSigner: "any",
42+
getEVMSigner: "any",
43+
getSVMSigner: "any",
44+
onWalletConnected: "any",
45+
onWalletDisconnected: "any",
46+
onTransactionBroadcasted: "any",
47+
onTransactionComplete: "any",
48+
onTransactionFailed: "any",
49+
onRouteUpdated: "any",
4250
};
4351

44-
const WEB_COMPONENT_NAME = "skip-widget";
45-
46-
const WebComponent = toWebComponent(WidgetWithProvider);
52+
const WebComponent = toWebComponent(Widget, {
53+
// @ts-expect-error any is not one of the valid types but it works
54+
props: widgetPropTypes,
55+
});
4756

4857
function initializeSkipWidget() {
4958
if (!customElements.get(WEB_COMPONENT_NAME)) {
5059
customElements.define(WEB_COMPONENT_NAME, WebComponent);
5160
}
5261

5362
// Upgrade any existing skip-widget elements
54-
document.querySelectorAll(WEB_COMPONENT_NAME).forEach((el) => {
55-
customElements.upgrade(el);
56-
});
63+
document.querySelectorAll(WEB_COMPONENT_NAME).forEach((el) => customElements.upgrade(el as Node));
5764
}
5865

5966
initializeSkipWidget();
6067

6168
export default WebComponent;
6269

63-
type Stringify<T> = {
64-
[K in keyof T]?: string;
65-
};
66-
6770
declare global {
68-
namespace JSX {
69-
interface IntrinsicElements {
70-
[WEB_COMPONENT_NAME]: Stringify<WebComponentProps>;
71-
}
71+
interface HTMLElementTagNameMap {
72+
[WEB_COMPONENT_NAME]: WidgetProps;
7273
}
7374
}

packages/widget/src/widget/Widget.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ type NewSwapVenueRequest = {
9797
chainId: string;
9898
};
9999

100-
type NewSkipClientOptions = Omit<SkipClientOptions, "apiURL" | "chainIDsToAffiliates"> & {
100+
export type NewSkipClientOptions = Omit<SkipClientOptions, "apiURL" | "chainIDsToAffiliates"> & {
101101
apiUrl?: string;
102102
chainIdsToAffiliates?: Record<string, ChainAffiliates>;
103103
};

0 commit comments

Comments
 (0)