Skip to content

Commit

Permalink
Add AccountSelector component (#2764)
Browse files Browse the repository at this point in the history
This PR adds an `AccountSelector` component.

Fixes: #2752
  • Loading branch information
GuillaumeRx authored Sep 26, 2024
1 parent 0014ff6 commit 73d00a0
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "OKpRMRDPOSMb+v1pNizzzKpUXrZIBLMJqXCh9xR3rFQ=",
"shasum": "+w62Op5ur4nVLmQ0uKA0IsAQN2hkKOsgSm4VK9jxxYY=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/examples/packages/browserify/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "9olSQCt4yqefE+yiWvkXwsB966J81losYyHKyLaVgKI=",
"shasum": "SL7kg2vwhtpuzNvOx7uQZNZbccRgfFtTi0xZdEVEP0s=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { AccountSelector } from './AccountSelector';

describe('AccountSelector', () => {
it('returns an account selector element', () => {
const result = (
<AccountSelector
name="account"
title="From account"
chainId="bip122:p2wpkh"
selectedAddress="bc1qc8dwyqua9elc3mzcxk93c70kjz8tcc92x0a8a6"
/>
);

expect(result).toStrictEqual({
type: 'AccountSelector',
props: {
name: 'account',
title: 'From account',
chainId: 'bip122:p2wpkh',
selectedAddress: 'bc1qc8dwyqua9elc3mzcxk93c70kjz8tcc92x0a8a6',
},
key: null,
});
});
});
52 changes: 52 additions & 0 deletions packages/snaps-sdk/src/jsx/components/form/AccountSelector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { CaipAccountAddress, CaipChainId } from '@metamask/utils';

import { createSnapComponent } from '../../component';

/**
* The props of the {@link AccountSelector} component.
*
* @property name - The name of the account selector. This is used to identify the
* state in the form data.
* @property title - The title of the account selector. This is displayed in the UI.
* @property chainId - The chain ID of the account selector. This should be a valid CAIP-2 chain ID.
* @property selectedAddress - The default selected address of the account selector. This should be a
* valid CAIP-10 account address.
*/
export type AccountSelectorProps = {
name: string;
title: string;
chainId: CaipChainId;
selectedAddress: CaipAccountAddress;
};

const TYPE = 'AccountSelector';

/**
* An account selector component, which is used to create an account selector.
*
* This component does not accept any children.
*
* @param props - The props of the component.
* @param props.name - The name of the account selector field. This is used to identify the
* state in the form data.
* @param props.title - The title of the account selector field. This is displayed in the UI.
* @param props.chainId - The chain ID of the account selector. This should be a valid CAIP-2 chain ID.
* @param props.selectedAddress - The selected address of the account selector. This should be a
* valid CAIP-10 account address.
* @returns An account selector element.
* @example
* <AccountSelector name="account" title="From account" chainId="eip155:1" selectedAddress="0x1234567890123456789012345678901234567890" />
* @example
* <AccountSelector name="account" title="From account" chainId="bip122:000000000019d6689c085ae165831e93" selectedAddress="128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6" />
*/
export const AccountSelector = createSnapComponent<
AccountSelectorProps,
typeof TYPE
>(TYPE);

/**
* An account selector element.
*
* @see AccountSelector
*/
export type AccountSelectorElement = ReturnType<typeof AccountSelector>;
3 changes: 3 additions & 0 deletions packages/snaps-sdk/src/jsx/components/form/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { AccountSelectorElement } from './AccountSelector';
import type { ButtonElement } from './Button';
import type { CheckboxElement } from './Checkbox';
import type { DropdownElement } from './Dropdown';
Expand All @@ -11,6 +12,7 @@ import type { RadioGroupElement } from './RadioGroup';
import type { SelectorElement } from './Selector';
import type { SelectorOptionElement } from './SelectorOption';

export * from './AccountSelector';
export * from './Button';
export * from './Checkbox';
export * from './Dropdown';
Expand All @@ -25,6 +27,7 @@ export * from './Selector';
export * from './SelectorOption';

export type StandardFormElement =
| AccountSelectorElement
| ButtonElement
| CheckboxElement
| FormElement
Expand Down
65 changes: 65 additions & 0 deletions packages/snaps-sdk/src/jsx/validation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
Selector,
SelectorOption,
Section,
AccountSelector,
} from './components';
import {
AddressStruct,
Expand Down Expand Up @@ -69,6 +70,7 @@ import {
SelectorStruct,
SectionStruct,
NotificationComponentsStruct,
AccountSelectorStruct,
} from './validation';

describe('KeyStruct', () => {
Expand Down Expand Up @@ -956,6 +958,69 @@ describe('FileInputStruct', () => {
});
});

describe('AccountSelectorStruct', () => {
it.each([
<AccountSelector
name="account"
title="From Account"
chainId="bip122:000000000019d6689c085ae165831e93"
selectedAddress="128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6"
/>,
<AccountSelector
name="account"
title="From Account"
chainId="eip155:1"
selectedAddress="0x1234567890123456789012345678901234567890"
/>,
])('validates an account picker element', (value) => {
expect(is(value, AccountSelectorStruct)).toBe(true);
});

it.each([
'foo',
42,
null,
undefined,
{},
[],
// @ts-expect-error - Invalid props.
<AccountSelector />,
// @ts-expect-error - Invalid props.
<AccountSelector>
<Text>foo</Text>
</AccountSelector>,
// @ts-expect-error - Invalid props.
<AccountSelector name="account" />,
// @ts-expect-error - Invalid props.
<AccountSelector title="From Account" />,
// @ts-expect-error - Invalid props.
<AccountSelector chainId="bip122:000000000019d6689c085ae165831e93" />,
// @ts-expect-error - Invalid props.
<AccountSelector selectedAddress="128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6" />,
<AccountSelector
name="account"
title="From Account"
chainId="foo:bar"
selectedAddress="0x1234567890123456789012345678901234567890"
/>,
<AccountSelector
name="account"
title="From Account"
chainId="eip155:1"
selectedAddress="0x123"
/>,
<Text>foo</Text>,
<Box>
<Text>foo</Text>
</Box>,
<Row label="label">
<Image src="<svg />" alt="alt" />
</Row>,
])('does not validate "%p"', (value) => {
expect(is(value, AddressStruct)).toBe(false);
});
});

describe('SelectorStruct', () => {
it.each([
<Selector name="foo" title="Title">
Expand Down
20 changes: 20 additions & 0 deletions packages/snaps-sdk/src/jsx/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import {
refine,
} from '@metamask/superstruct';
import {
CaipAccountAddressStruct,
CaipAccountIdStruct,
CaipChainIdStruct,
hasProperty,
HexChecksumAddressStruct,
isPlainObject,
Expand All @@ -47,6 +49,7 @@ import type {
StringElement,
} from './component';
import {
type AccountSelectorElement,
type AddressElement,
type BoldElement,
type BoxElement,
Expand Down Expand Up @@ -331,6 +334,22 @@ export const FileInputStruct: Describe<FileInputElement> = element(
},
);

/**
* A struct for the {@link AccountSelectorElement} type.
*/
export const AccountSelectorStruct: Describe<AccountSelectorElement> = element(
'AccountSelector',
{
name: string(),
title: string(),
chainId: CaipChainIdStruct as unknown as Struct<
Infer<typeof CaipChainIdStruct>,
Infer<typeof CaipChainIdStruct>
>,
selectedAddress: CaipAccountAddressStruct,
},
);

/**
* A subset of JSX elements that represent the tuple Box + Input of the Field children.
*/
Expand Down Expand Up @@ -824,6 +843,7 @@ export const JSXElementStruct: Describe<JSXElement> = typedUnion([
SelectorStruct,
SelectorOptionStruct,
SectionStruct,
AccountSelectorStruct,
]);

/**
Expand Down

0 comments on commit 73d00a0

Please sign in to comment.