From 2564a9753117ee47d5a591a157399b3dabcae425 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri <1247834+brunobar79@users.noreply.github.com> Date: Wed, 6 Dec 2023 15:07:49 -0500 Subject: [PATCH] Network Autocomplete (#1177) Co-authored-by: gregs --- package.json | 3 +- .../popup/components/Autocomplete/index.tsx | 155 ++++++++++++++++++ .../pages/settings/customChain/index.tsx | 106 +++++++++--- static/json/languages/en_US.json | 11 +- yarn.lock | 99 +++++++++++ 5 files changed, 351 insertions(+), 23 deletions(-) create mode 100644 src/entries/popup/components/Autocomplete/index.tsx diff --git a/package.json b/package.json index c48efd9a51..ad556820f7 100644 --- a/package.json +++ b/package.json @@ -123,6 +123,7 @@ "check-password-strength": "2.0.7", "chroma-js": "2.4.2", "clsx": "1.2.1", + "cmdk": "0.2.0", "currency.js": "2.0.4", "date-fns": "2.29.3", "deepmerge": "4.2.2", @@ -310,4 +311,4 @@ "wagmi>@wagmi/core>@wagmi/connectors>@coinbase/wallet-sdk>keccak": false } } -} \ No newline at end of file +} diff --git a/src/entries/popup/components/Autocomplete/index.tsx b/src/entries/popup/components/Autocomplete/index.tsx new file mode 100644 index 0000000000..c4c6a05b99 --- /dev/null +++ b/src/entries/popup/components/Autocomplete/index.tsx @@ -0,0 +1,155 @@ +import { Command } from 'cmdk'; +import { forwardRef } from 'react'; + +import { Box } from '~/design-system'; +import { stylesForHeight } from '~/design-system/components/Input/Input'; +import { BoxStyles, semanticColorVars } from '~/design-system/styles/core.css'; + +export interface AutocompleteItem { + name: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value?: any; +} + +export interface AutocompleteData { + [key: string]: AutocompleteItem[]; +} + +export interface AutocompleteProps { + data: AutocompleteData; + value: string; + onChange: (value: string) => void; + onSelect: (value: string) => void; + onBlur: () => void; + onFocus: () => void; + borderColor?: BoxStyles['borderColor']; + placeholder?: string; + open: boolean; +} + +export const Autocomplete = forwardRef( + ( + { + data, + value, + onChange, + onSelect, + onBlur, + onFocus, + borderColor, + placeholder, + open, + }: AutocompleteProps, + ref, + ) => { + const { + borderRadius: defaultBorderRadius, + fontSize: defaultFontSize, + paddingHorizontal, + paddingVertical, + } = stylesForHeight['32px']; + + return ( + + + + setTimeout(onBlur, 200)} + onFocus={onFocus} + ref={ref} + /> + + {open && ( + + + {Object.keys(data).map((key) => { + return ( + + + {data[key].map((item: { name: string }) => ( + + onSelect(item.name)} + value={item.name} + onMouseOver={(e) => { + e.currentTarget.style.backgroundColor = + semanticColorVars.backgroundColors.surfaceSecondaryElevated; + }} + onMouseLeave={(e) => { + e.currentTarget.style.backgroundColor = + semanticColorVars.backgroundColors.surfacePrimaryElevated; + }} + > + {item.name} + + + ))} + + + ); + })} + + + )} + + + ); + }, +); + +Autocomplete.displayName = 'Autocomplete'; diff --git a/src/entries/popup/pages/settings/customChain/index.tsx b/src/entries/popup/pages/settings/customChain/index.tsx index 8ecdcd0db9..a16fb44814 100644 --- a/src/entries/popup/pages/settings/customChain/index.tsx +++ b/src/entries/popup/pages/settings/customChain/index.tsx @@ -1,12 +1,14 @@ import { isEqual } from 'lodash'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { Chain } from 'wagmi'; +import { i18n } from '~/core/languages'; import { useChainMetadata } from '~/core/resources/chains/chainMetadata'; import { useCustomRPCsStore } from '~/core/state/customRPC'; import { useUserChainsStore } from '~/core/state/userChains'; import { isValidUrl } from '~/core/utils/connectedApps'; import { Box, Button, Inline, Stack, Text } from '~/design-system'; +import { Autocomplete } from '~/entries/popup/components/Autocomplete'; import { Form } from '~/entries/popup/components/Form/Form'; import { FormInput } from '~/entries/popup/components/Form/FormInput'; import { useDebounce } from '~/entries/popup/hooks/useDebounce'; @@ -17,10 +19,36 @@ import { ROUTES } from '~/entries/popup/urls'; import { Checkbox } from '../../../components/Checkbox/Checkbox'; import { maskInput } from '../../../components/InputMask/utils'; +const KNOWN_NETWORKS = { + [i18n.t('settings.networks.custom_rpc.networks')]: [ + { + name: 'Avalanche', + value: { + rpcUrl: 'https://api.avax.network/ext/bc/C/rpc', + chainId: 43114, + decimals: 18, + symbol: 'AVAX', + explorerUrl: 'https://cchain.explorer.avax.network', + }, + }, + { + name: 'PulseChain', + value: { + rpcUrl: 'https://rpc.pulsechain.com', + chainId: 369, + decimals: 18, + symbol: 'PULSE', + explorerUrl: 'https://pulsechain.com', + }, + }, + ], +}; + export function SettingsCustomChain() { const navigate = useRainbowNavigate(); const { customChains, addCustomRPC } = useCustomRPCsStore(); const { addUserChain } = useUserChainsStore(); + const [open, setOpen] = useState(false); const [customRPC, setCustomRPC] = useState<{ active?: boolean; rpcUrl?: string; @@ -43,6 +71,7 @@ export function SettingsCustomChain() { explorerUrl: true, }); const debuncedRpcUrl = useDebounce(customRPC.rpcUrl, 500); + const inputRef = useRef(null); const { data: chainMetadata, @@ -101,12 +130,14 @@ export function SettingsCustomChain() { [validateChainId], ); - const validateName = useCallback(() => !!customRPC.name, [customRPC.name]); + const validateName = useCallback(() => { + return !!inputRef.current?.value; + }, []); - const onNameBlur = useCallback( - () => setValidations((prev) => ({ ...prev, name: validateName() })), - [validateName], - ); + const onNameBlur = useCallback(() => { + setValidations((prev) => ({ ...prev, name: validateName() })); + open && setOpen(false); + }, [open, validateName]); const validateSymbol = useCallback( () => !!customRPC.symbol, @@ -216,6 +247,34 @@ export function SettingsCustomChain() { validateCustomRpcMetadata, ]); + const handleNetworkSelect = useCallback( + (networkName: string) => { + const network = KNOWN_NETWORKS.Networks.find( + (network) => network.name === networkName, + ); + if (network) { + setCustomRPC((prev) => ({ + ...prev, + ...network.value, + name: networkName, + active: true, + })); + + // All these are previously validated by us + // when adding the network to the list + setValidations({ + rpcUrl: true, + chainId: true, + name: true, + symbol: true, + explorerUrl: true, + }); + } + open && setOpen(false); + }, + [open], + ); + return ( @@ -261,11 +320,23 @@ export function SettingsCustomChain() { ))}
+ setOpen(true)} + onBlur={onNameBlur} + data={KNOWN_NETWORKS} + value={customRPC.name || ''} + borderColor={validations.name ? 'accent' : 'red'} + placeholder={i18n.t('settings.networks.custom_rpc.network_name')} + onChange={(value) => onInputChange(value, 'string', 'name')} + onSelect={handleNetworkSelect} + ref={inputRef} + /> onInputChange(t.target.value, 'string', 'rpcUrl') } - placeholder="Url" + placeholder={i18n.t('settings.networks.custom_rpc.rpc_url')} value={customRPC.rpcUrl} onBlur={onRpcUrlBlur} borderColor={ @@ -277,25 +348,16 @@ export function SettingsCustomChain() { onChange={(t) => onInputChange(t.target.value, 'number', 'chainId') } - placeholder="ChainId" + placeholder={i18n.t('settings.networks.custom_rpc.chain_id')} value={customRPC.chainId || chainMetadata?.chainId || ''} onBlur={onChainIdBlur} borderColor={validations.chainId ? 'accent' : 'red'} /> - - onInputChange(t.target.value, 'string', 'name') - } - placeholder="name" - value={customRPC.name} - onBlur={onNameBlur} - borderColor={validations.name ? 'accent' : 'red'} - /> onInputChange(t.target.value, 'string', 'symbol') } - placeholder="Symbol" + placeholder={i18n.t('settings.networks.custom_rpc.symbol')} value={customRPC.symbol} onBlur={onSymbolBlur} borderColor={validations.symbol ? 'accent' : 'red'} @@ -304,7 +366,9 @@ export function SettingsCustomChain() { onChange={(t) => onInputChange(t.target.value, 'string', 'explorerUrl') } - placeholder="Explorer url" + placeholder={i18n.t( + 'settings.networks.custom_rpc.block_explorer_url', + )} value={customRPC.explorerUrl} onBlur={onExplorerUrlBlur} borderColor={validations.explorerUrl ? 'accent' : 'red'} @@ -317,7 +381,7 @@ export function SettingsCustomChain() { size="12pt" color="labelSecondary" > - {'Active'} + {i18n.t('settings.networks.custom_rpc.active')} - Add + {i18n.t('settings.networks.custom_rpc.add_network')} diff --git a/static/json/languages/en_US.json b/static/json/languages/en_US.json index a1a1fcd869..4d0c0556aa 100644 --- a/static/json/languages/en_US.json +++ b/static/json/languages/en_US.json @@ -98,7 +98,16 @@ "toggle_explainer": "Enables the Testnet Mode shortcut (T) and adds a toggle to the home screen’s top-right menu." }, "custom_rpc": { - "title": "Custom RPC" + "title": "Custom RPC", + "networks": "Networks", + "network_name": "Network Name", + "rpc_url": "RPC URL", + "chain_id": "Chain ID", + "symbol": "Symbol", + "decimals": "Decimals", + "active": "Active", + "block_explorer_url": "Block Explorer URL", + "add_network": "Add Network" } }, "privacy_and_security": { diff --git a/yarn.lock b/yarn.lock index 3ec86cda45..ab3cc88d2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2751,6 +2751,27 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-dialog@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.0.tgz#997e97cb183bc90bd888b26b8e23a355ac9fe5f0" + integrity sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.0" + "@radix-ui/react-compose-refs" "1.0.0" + "@radix-ui/react-context" "1.0.0" + "@radix-ui/react-dismissable-layer" "1.0.0" + "@radix-ui/react-focus-guards" "1.0.0" + "@radix-ui/react-focus-scope" "1.0.0" + "@radix-ui/react-id" "1.0.0" + "@radix-ui/react-portal" "1.0.0" + "@radix-ui/react-presence" "1.0.0" + "@radix-ui/react-primitive" "1.0.0" + "@radix-ui/react-slot" "1.0.0" + "@radix-ui/react-use-controllable-state" "1.0.0" + aria-hidden "^1.1.1" + react-remove-scroll "2.5.4" + "@radix-ui/react-direction@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.0.tgz#a2e0b552352459ecf96342c79949dd833c1e6e45" @@ -2765,6 +2786,18 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-dismissable-layer@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz#35b7826fa262fd84370faef310e627161dffa76b" + integrity sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.0" + "@radix-ui/react-compose-refs" "1.0.0" + "@radix-ui/react-primitive" "1.0.0" + "@radix-ui/react-use-callback-ref" "1.0.0" + "@radix-ui/react-use-escape-keydown" "1.0.0" + "@radix-ui/react-dismissable-layer@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.2.tgz#f04d1061bddf00b1ca304148516b9ddc62e45fb2" @@ -2810,6 +2843,16 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-focus-scope@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz#95a0c1188276dc8933b1eac5f1cdb6471e01ade5" + integrity sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.0" + "@radix-ui/react-primitive" "1.0.0" + "@radix-ui/react-use-callback-ref" "1.0.0" + "@radix-ui/react-focus-scope@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.1.tgz#faea8c25f537c5a5c38c50914b63722db0e7f951" @@ -2951,6 +2994,14 @@ "@radix-ui/react-use-size" "1.0.0" "@radix-ui/rect" "1.0.0" +"@radix-ui/react-portal@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.0.tgz#7220b66743394fabb50c55cb32381395cc4a276b" + integrity sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.0" + "@radix-ui/react-portal@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.1.tgz#169c5a50719c2bb0079cf4c91a27aa6d37e5dd33" @@ -2985,6 +3036,14 @@ "@radix-ui/react-compose-refs" "1.0.1" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-primitive@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz#376cd72b0fcd5e0e04d252ed33eb1b1f025af2b0" + integrity sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-slot" "1.0.0" + "@radix-ui/react-primitive@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.1.tgz#c1ebcce283dd2f02e4fbefdaa49d1cb13dbc990a" @@ -3085,6 +3144,14 @@ aria-hidden "^1.1.1" react-remove-scroll "2.5.5" +"@radix-ui/react-slot@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.0.tgz#7fa805b99891dea1e862d8f8fbe07f4d6d0fd698" + integrity sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.0" + "@radix-ui/react-slot@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.1.tgz#e7868c669c974d649070e9ecbec0b367ee0b4d81" @@ -3165,6 +3232,14 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-use-escape-keydown@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz#aef375db4736b9de38a5a679f6f49b45a060e5d1" + integrity sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-callback-ref" "1.0.0" + "@radix-ui/react-use-escape-keydown@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.2.tgz#09ab6455ab240b4f0a61faf06d4e5132c4d639f6" @@ -6522,6 +6597,14 @@ cmd-shim@^6.0.0: resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.1.tgz#a65878080548e1dca760b3aea1e21ed05194da9d" integrity sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q== +cmdk@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/cmdk/-/cmdk-0.2.0.tgz#53c52d56d8776c8bb8ced1055b5054100c388f7c" + integrity sha512-JQpKvEOb86SnvMZbYaFKYhvzFntWBeSZdyii0rZPhKJj9uwJBxu4DaVYDrRN7r3mPop56oPhRw+JYWTKs66TYw== + dependencies: + "@radix-ui/react-dialog" "1.0.0" + command-score "0.1.2" + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -6606,6 +6689,11 @@ command-exists@^1.2.9: resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== +command-score@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/command-score/-/command-score-0.1.2.tgz#b986ad7e8c0beba17552a56636c44ae38363d381" + integrity sha512-VtDvQpIJBvBatnONUsPzXYFVKQQAhuf3XTNOAsdBxCNO/QCtUUd8LSgjn0GVarBkCad6aJCZfXgrjYbl/KRr7w== + commander@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" @@ -13405,6 +13493,17 @@ react-remove-scroll-bar@^2.3.3: react-style-singleton "^2.2.1" tslib "^2.0.0" +react-remove-scroll@2.5.4: + version "2.5.4" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz#afe6491acabde26f628f844b67647645488d2ea0" + integrity sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA== + dependencies: + react-remove-scroll-bar "^2.3.3" + react-style-singleton "^2.2.1" + tslib "^2.1.0" + use-callback-ref "^1.3.0" + use-sidecar "^1.1.2" + react-remove-scroll@2.5.5: version "2.5.5" resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz#1e31a1260df08887a8a0e46d09271b52b3a37e77"