Skip to content

Commit 01bac08

Browse files
authored
Add basic version of MultiAddressInput (#296)
* Add basic version of MultiAddressInput * Add credits for original authors * Add a simple docstring and update placeholder * Add changeset
1 parent f95d2dd commit 01bac08

File tree

6 files changed

+163
-0
lines changed

6 files changed

+163
-0
lines changed

.changeset/purple-hats-battle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@web3-ui/components': minor
3+
---
4+
5+
Add `MultiAddressInput` component

packages/components/src/assets/.gitkeep

Whitespace-only changes.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React, { useEffect } from 'react';
2+
import { NETWORKS, Provider, useReadOnlyProvider } from '@web3-ui/hooks';
3+
import { MultiAddressInput, Entry } from './MultiAddressInput';
4+
5+
export default {
6+
title: 'Components/MultiAddressInput',
7+
component: MultiAddressInput,
8+
};
9+
10+
const AddressUsingProvider = () => {
11+
const mainnetProvider = useReadOnlyProvider(
12+
'https://mainnet.infura.io/v3/21bc321f21a54c528dc084f5ed7f8df7'
13+
);
14+
const [value, setValue] = React.useState<Entry[]>([]);
15+
16+
useEffect(() => {
17+
console.log({ value });
18+
}, [value]);
19+
20+
if (!mainnetProvider) {
21+
return null;
22+
}
23+
24+
return (
25+
<>
26+
<MultiAddressInput
27+
value={value}
28+
onChange={setValue}
29+
ensProvider={mainnetProvider}
30+
placeholder="Enter ENS names or addresses"
31+
/>
32+
</>
33+
);
34+
};
35+
36+
export const WithWallet = () => (
37+
<Provider network={NETWORKS.rinkeby}>
38+
<AddressUsingProvider />
39+
</Provider>
40+
);
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import {
2+
Box,
3+
Textarea,
4+
HStack,
5+
Tag,
6+
TagLabel,
7+
TagCloseButton,
8+
Wrap,
9+
Spinner,
10+
VStack,
11+
TextareaProps,
12+
} from '@chakra-ui/react';
13+
import React, { ChangeEvent, FC, useState } from 'react';
14+
import { ethers } from 'ethers';
15+
16+
export type Entry = {
17+
address: string | null;
18+
ens: string | null;
19+
input: string;
20+
isValid: boolean;
21+
};
22+
23+
export interface MultiAddressInputProps {
24+
ensProvider: ethers.providers.EnsProvider;
25+
value: Entry[];
26+
onChange: (entries: Entry[]) => void;
27+
placeholder?: string;
28+
inputProps?: TextareaProps;
29+
}
30+
31+
/*
32+
* Original authors: Hans and Bliss from the Moonshot Collective
33+
* Original source code: https://github.com/moonshotcollective/pay.party/blob/develop/packages/react-app/src/routes/create/components/MultiAddressInput.jsx
34+
* @dev This component lets you input multiple addresses and ENS names.
35+
*/
36+
export const MultiAddressInput: FC<MultiAddressInputProps> = ({
37+
ensProvider,
38+
value,
39+
onChange,
40+
placeholder = 'Enter ENS names or addresses here',
41+
inputProps,
42+
}) => {
43+
const [isLoading, setIsLoading] = useState(false);
44+
45+
const addressBadge = (entry: Entry) => {
46+
return (
47+
<Box p="1" key={entry.input}>
48+
<HStack spacing={4}>
49+
<Tag size="md" key="md" borderRadius="full" variant="solid">
50+
<TagLabel color={entry.isValid ? 'default' : 'red.300'}>
51+
{entry.input}
52+
</TagLabel>
53+
<TagCloseButton
54+
onClick={() => {
55+
onChange(value.filter((obj) => obj.input !== entry.input));
56+
}}
57+
/>
58+
</Tag>
59+
</HStack>
60+
</Box>
61+
);
62+
};
63+
64+
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
65+
const lastInput = e.target.value[e.target.value.length - 1];
66+
if (lastInput === ',' || lastInput === '\n') {
67+
const splitInput = e.currentTarget.value
68+
.split(/[ ,\n]+/)
69+
.filter((c: string) => c !== '')
70+
.map(async (uin: string) => {
71+
const val: Entry = {
72+
input: uin,
73+
isValid: false,
74+
address: null,
75+
ens: null,
76+
};
77+
try {
78+
if (uin.endsWith('.eth') || uin.endsWith('.xyz')) {
79+
val.address = await ensProvider.resolveName(uin);
80+
val.ens = uin;
81+
} else {
82+
val.ens = await ensProvider.lookupAddress(uin);
83+
val.address = uin;
84+
}
85+
val.isValid = true;
86+
} catch {
87+
val.isValid = false;
88+
console.error('Bad Address: ' + uin);
89+
}
90+
return val;
91+
});
92+
setIsLoading(true);
93+
Promise.all(splitInput)
94+
.then((d) => {
95+
onChange([...value, ...d]);
96+
})
97+
.finally(() => setIsLoading(false));
98+
e.target.value = '';
99+
}
100+
};
101+
102+
return (
103+
<VStack>
104+
<Wrap>{value && value.map(addressBadge)}</Wrap>
105+
<Textarea
106+
rows={1}
107+
placeholder={placeholder}
108+
onChange={handleChange}
109+
disabled={isLoading}
110+
{...inputProps}
111+
/>
112+
{isLoading && <Spinner size="sm" />}
113+
</VStack>
114+
);
115+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { MultiAddressInput } from './MultiAddressInput';
2+
export type { MultiAddressInputProps, Entry } from './MultiAddressInput';

packages/components/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export * from './AddressInput';
88
export * from './TokenGate';
99
export * from './EtherInput';
1010
export * from './TokenBalance';
11+
export * from './MultiAddressInput';

0 commit comments

Comments
 (0)