From 39170f3f8271e0947a0de83ce217cb2e83531497 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Thu, 26 Sep 2024 13:03:00 +0200 Subject: [PATCH 1/3] Add `AccountPicker` component --- .../components/form/AccountPicker.test.tsx | 25 +++++++ .../src/jsx/components/form/AccountPicker.tsx | 52 +++++++++++++++ .../src/jsx/components/form/index.ts | 3 + .../snaps-sdk/src/jsx/validation.test.tsx | 65 +++++++++++++++++++ packages/snaps-sdk/src/jsx/validation.ts | 16 +++++ 5 files changed, 161 insertions(+) create mode 100644 packages/snaps-sdk/src/jsx/components/form/AccountPicker.test.tsx create mode 100644 packages/snaps-sdk/src/jsx/components/form/AccountPicker.tsx diff --git a/packages/snaps-sdk/src/jsx/components/form/AccountPicker.test.tsx b/packages/snaps-sdk/src/jsx/components/form/AccountPicker.test.tsx new file mode 100644 index 0000000000..028dfa967a --- /dev/null +++ b/packages/snaps-sdk/src/jsx/components/form/AccountPicker.test.tsx @@ -0,0 +1,25 @@ +import { AccountPicker } from './AccountPicker'; + +describe('AccountPicker', () => { + it('returns an account picker element', () => { + const result = ( + + ); + + expect(result).toStrictEqual({ + type: 'AccountPicker', + props: { + name: 'account', + title: 'From account', + chainId: 'bip122:p2wpkh', + selectedAddress: 'bc1qc8dwyqua9elc3mzcxk93c70kjz8tcc92x0a8a6', + }, + key: null, + }); + }); +}); diff --git a/packages/snaps-sdk/src/jsx/components/form/AccountPicker.tsx b/packages/snaps-sdk/src/jsx/components/form/AccountPicker.tsx new file mode 100644 index 0000000000..5e19321934 --- /dev/null +++ b/packages/snaps-sdk/src/jsx/components/form/AccountPicker.tsx @@ -0,0 +1,52 @@ +import type { CaipAccountAddress, CaipChainId } from '@metamask/utils'; + +import { createSnapComponent } from '../../component'; + +/** + * The props of the {@link AccountPicker} component. + * + * @property name - The name of the account picker. This is used to identify the + * state in the form data. + * @property title - The title of the account picker. This is displayed in the UI. + * @property chainId - The chain ID of the account picker. This should be a valid CAIP-2 chain ID. + * @property selectedAddress - The default selected address of the account picker. This should be a + * valid CAIP-10 account address. + */ +export type AccountPickerProps = { + name: string; + title: string; + chainId: CaipChainId; + selectedAddress: CaipAccountAddress; +}; + +const TYPE = 'AccountPicker'; + +/** + * An account picker component, which is used to create an account picker. + * + * This component does not accept any children. + * + * @param props - The props of the component. + * @param props.name - The name of the account picker field. This is used to identify the + * state in the form data. + * @param props.title - The title of the account picker field. This is displayed in the UI. + * @param props.chainId - The chain ID of the account picker. This should be a valid CAIP-2 chain ID. + * @param props.selectedAddress - The selected address of the account picker. This should be a + * valid CAIP-10 account address. + * @returns An account picker element. + * @example + * + * @example + * + */ +export const AccountPicker = createSnapComponent< + AccountPickerProps, + typeof TYPE +>(TYPE); + +/** + * An account picker element. + * + * @see AccountPicker + */ +export type AccountPickerElement = ReturnType; diff --git a/packages/snaps-sdk/src/jsx/components/form/index.ts b/packages/snaps-sdk/src/jsx/components/form/index.ts index 3dc4221376..5542eedba2 100644 --- a/packages/snaps-sdk/src/jsx/components/form/index.ts +++ b/packages/snaps-sdk/src/jsx/components/form/index.ts @@ -1,3 +1,4 @@ +import type { AccountPickerElement } from './AccountPicker'; import type { ButtonElement } from './Button'; import type { CheckboxElement } from './Checkbox'; import type { DropdownElement } from './Dropdown'; @@ -11,6 +12,7 @@ import type { RadioGroupElement } from './RadioGroup'; import type { SelectorElement } from './Selector'; import type { SelectorOptionElement } from './SelectorOption'; +export * from './AccountPicker'; export * from './Button'; export * from './Checkbox'; export * from './Dropdown'; @@ -25,6 +27,7 @@ export * from './Selector'; export * from './SelectorOption'; export type StandardFormElement = + | AccountPickerElement | ButtonElement | CheckboxElement | FormElement diff --git a/packages/snaps-sdk/src/jsx/validation.test.tsx b/packages/snaps-sdk/src/jsx/validation.test.tsx index c58e462d1d..e4596974c1 100644 --- a/packages/snaps-sdk/src/jsx/validation.test.tsx +++ b/packages/snaps-sdk/src/jsx/validation.test.tsx @@ -32,6 +32,7 @@ import { Selector, SelectorOption, Section, + AccountPicker, } from './components'; import { AddressStruct, @@ -69,6 +70,7 @@ import { SelectorStruct, SectionStruct, NotificationComponentsStruct, + AccountPickerStruct, } from './validation'; describe('KeyStruct', () => { @@ -956,6 +958,69 @@ describe('FileInputStruct', () => { }); }); +describe('AccountPickerStruct', () => { + it.each([ + , + , + ])('validates an account picker element', (value) => { + expect(is(value, AccountPickerStruct)).toBe(true); + }); + + it.each([ + 'foo', + 42, + null, + undefined, + {}, + [], + // @ts-expect-error - Invalid props. + , + // @ts-expect-error - Invalid props. + + foo + , + // @ts-expect-error - Invalid props. + , + // @ts-expect-error - Invalid props. + , + // @ts-expect-error - Invalid props. + , + // @ts-expect-error - Invalid props. + , + , + , + foo, + + foo + , + + alt + , + ])('does not validate "%p"', (value) => { + expect(is(value, AddressStruct)).toBe(false); + }); +}); + describe('SelectorStruct', () => { it.each([ diff --git a/packages/snaps-sdk/src/jsx/validation.ts b/packages/snaps-sdk/src/jsx/validation.ts index 8da02b5080..fd147dd8af 100644 --- a/packages/snaps-sdk/src/jsx/validation.ts +++ b/packages/snaps-sdk/src/jsx/validation.ts @@ -20,7 +20,9 @@ import { refine, } from '@metamask/superstruct'; import { + CaipAccountAddressStruct, CaipAccountIdStruct, + CaipChainIdStruct, hasProperty, HexChecksumAddressStruct, isPlainObject, @@ -47,6 +49,7 @@ import type { StringElement, } from './component'; import { + type AccountPickerElement, type AddressElement, type BoldElement, type BoxElement, @@ -331,6 +334,19 @@ export const FileInputStruct: Describe = element( }, ); +/** + * A struct for the {@link AccountPickerElement} type. + */ +export const AccountPickerStruct: Describe = element( + 'AccountPicker', + { + name: string(), + title: string(), + chainId: CaipChainIdStruct, + selectedAddress: CaipAccountAddressStruct, + }, +); + /** * A subset of JSX elements that represent the tuple Box + Input of the Field children. */ From 5f585c0b37387971872ac47783eb0334f3d00c8e Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Thu, 26 Sep 2024 14:19:54 +0200 Subject: [PATCH 2/3] Rename to `AccountSelector` --- .../browserify-plugin/snap.manifest.json | 2 +- .../packages/browserify/snap.manifest.json | 2 +- .../src/jsx/components/form/AccountPicker.tsx | 52 ------------------- ...cker.test.tsx => AccountSelector.test.tsx} | 10 ++-- .../jsx/components/form/AccountSelector.tsx | 52 +++++++++++++++++++ .../src/jsx/components/form/index.ts | 6 +-- .../snaps-sdk/src/jsx/validation.test.tsx | 30 +++++------ packages/snaps-sdk/src/jsx/validation.ts | 14 +++-- 8 files changed, 86 insertions(+), 82 deletions(-) delete mode 100644 packages/snaps-sdk/src/jsx/components/form/AccountPicker.tsx rename packages/snaps-sdk/src/jsx/components/form/{AccountPicker.test.tsx => AccountSelector.test.tsx} (69%) create mode 100644 packages/snaps-sdk/src/jsx/components/form/AccountSelector.tsx diff --git a/packages/examples/packages/browserify-plugin/snap.manifest.json b/packages/examples/packages/browserify-plugin/snap.manifest.json index ef573ca559..5fe60b987f 100644 --- a/packages/examples/packages/browserify-plugin/snap.manifest.json +++ b/packages/examples/packages/browserify-plugin/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "OKpRMRDPOSMb+v1pNizzzKpUXrZIBLMJqXCh9xR3rFQ=", + "shasum": "+w62Op5ur4nVLmQ0uKA0IsAQN2hkKOsgSm4VK9jxxYY=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/browserify/snap.manifest.json b/packages/examples/packages/browserify/snap.manifest.json index 601709e1ac..ae76aea210 100644 --- a/packages/examples/packages/browserify/snap.manifest.json +++ b/packages/examples/packages/browserify/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "9olSQCt4yqefE+yiWvkXwsB966J81losYyHKyLaVgKI=", + "shasum": "SL7kg2vwhtpuzNvOx7uQZNZbccRgfFtTi0xZdEVEP0s=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/snaps-sdk/src/jsx/components/form/AccountPicker.tsx b/packages/snaps-sdk/src/jsx/components/form/AccountPicker.tsx deleted file mode 100644 index 5e19321934..0000000000 --- a/packages/snaps-sdk/src/jsx/components/form/AccountPicker.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import type { CaipAccountAddress, CaipChainId } from '@metamask/utils'; - -import { createSnapComponent } from '../../component'; - -/** - * The props of the {@link AccountPicker} component. - * - * @property name - The name of the account picker. This is used to identify the - * state in the form data. - * @property title - The title of the account picker. This is displayed in the UI. - * @property chainId - The chain ID of the account picker. This should be a valid CAIP-2 chain ID. - * @property selectedAddress - The default selected address of the account picker. This should be a - * valid CAIP-10 account address. - */ -export type AccountPickerProps = { - name: string; - title: string; - chainId: CaipChainId; - selectedAddress: CaipAccountAddress; -}; - -const TYPE = 'AccountPicker'; - -/** - * An account picker component, which is used to create an account picker. - * - * This component does not accept any children. - * - * @param props - The props of the component. - * @param props.name - The name of the account picker field. This is used to identify the - * state in the form data. - * @param props.title - The title of the account picker field. This is displayed in the UI. - * @param props.chainId - The chain ID of the account picker. This should be a valid CAIP-2 chain ID. - * @param props.selectedAddress - The selected address of the account picker. This should be a - * valid CAIP-10 account address. - * @returns An account picker element. - * @example - * - * @example - * - */ -export const AccountPicker = createSnapComponent< - AccountPickerProps, - typeof TYPE ->(TYPE); - -/** - * An account picker element. - * - * @see AccountPicker - */ -export type AccountPickerElement = ReturnType; diff --git a/packages/snaps-sdk/src/jsx/components/form/AccountPicker.test.tsx b/packages/snaps-sdk/src/jsx/components/form/AccountSelector.test.tsx similarity index 69% rename from packages/snaps-sdk/src/jsx/components/form/AccountPicker.test.tsx rename to packages/snaps-sdk/src/jsx/components/form/AccountSelector.test.tsx index 028dfa967a..7904e60cbf 100644 --- a/packages/snaps-sdk/src/jsx/components/form/AccountPicker.test.tsx +++ b/packages/snaps-sdk/src/jsx/components/form/AccountSelector.test.tsx @@ -1,9 +1,9 @@ -import { AccountPicker } from './AccountPicker'; +import { AccountSelector } from './AccountSelector'; -describe('AccountPicker', () => { - it('returns an account picker element', () => { +describe('AccountSelector', () => { + it('returns an account selector element', () => { const result = ( - { ); expect(result).toStrictEqual({ - type: 'AccountPicker', + type: 'AccountSelector', props: { name: 'account', title: 'From account', diff --git a/packages/snaps-sdk/src/jsx/components/form/AccountSelector.tsx b/packages/snaps-sdk/src/jsx/components/form/AccountSelector.tsx new file mode 100644 index 0000000000..78f54cf63b --- /dev/null +++ b/packages/snaps-sdk/src/jsx/components/form/AccountSelector.tsx @@ -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 + * + * @example + * + */ +export const AccountSelector = createSnapComponent< + AccountSelectorProps, + typeof TYPE +>(TYPE); + +/** + * An account selector element. + * + * @see AccountSelector + */ +export type AccountSelectorElement = ReturnType; diff --git a/packages/snaps-sdk/src/jsx/components/form/index.ts b/packages/snaps-sdk/src/jsx/components/form/index.ts index 5542eedba2..4e47f9e9d0 100644 --- a/packages/snaps-sdk/src/jsx/components/form/index.ts +++ b/packages/snaps-sdk/src/jsx/components/form/index.ts @@ -1,4 +1,4 @@ -import type { AccountPickerElement } from './AccountPicker'; +import type { AccountSelectorElement } from './AccountSelector'; import type { ButtonElement } from './Button'; import type { CheckboxElement } from './Checkbox'; import type { DropdownElement } from './Dropdown'; @@ -12,7 +12,7 @@ import type { RadioGroupElement } from './RadioGroup'; import type { SelectorElement } from './Selector'; import type { SelectorOptionElement } from './SelectorOption'; -export * from './AccountPicker'; +export * from './AccountSelector'; export * from './Button'; export * from './Checkbox'; export * from './Dropdown'; @@ -27,7 +27,7 @@ export * from './Selector'; export * from './SelectorOption'; export type StandardFormElement = - | AccountPickerElement + | AccountSelectorElement | ButtonElement | CheckboxElement | FormElement diff --git a/packages/snaps-sdk/src/jsx/validation.test.tsx b/packages/snaps-sdk/src/jsx/validation.test.tsx index e4596974c1..ff42634d56 100644 --- a/packages/snaps-sdk/src/jsx/validation.test.tsx +++ b/packages/snaps-sdk/src/jsx/validation.test.tsx @@ -32,7 +32,7 @@ import { Selector, SelectorOption, Section, - AccountPicker, + AccountSelector, } from './components'; import { AddressStruct, @@ -70,7 +70,7 @@ import { SelectorStruct, SectionStruct, NotificationComponentsStruct, - AccountPickerStruct, + AccountSelectorStruct, } from './validation'; describe('KeyStruct', () => { @@ -958,22 +958,22 @@ describe('FileInputStruct', () => { }); }); -describe('AccountPickerStruct', () => { +describe('AccountSelectorStruct', () => { it.each([ - , - , ])('validates an account picker element', (value) => { - expect(is(value, AccountPickerStruct)).toBe(true); + expect(is(value, AccountSelectorStruct)).toBe(true); }); it.each([ @@ -984,26 +984,26 @@ describe('AccountPickerStruct', () => { {}, [], // @ts-expect-error - Invalid props. - , + , // @ts-expect-error - Invalid props. - + foo - , + , // @ts-expect-error - Invalid props. - , + , // @ts-expect-error - Invalid props. - , + , // @ts-expect-error - Invalid props. - , + , // @ts-expect-error - Invalid props. - , - , + , - = element( ); /** - * A struct for the {@link AccountPickerElement} type. + * A struct for the {@link AccountSelectorElement} type. */ -export const AccountPickerStruct: Describe = element( - 'AccountPicker', +export const AccountSelectorStruct: Describe = element( + 'AccountSelector', { name: string(), title: string(), - chainId: CaipChainIdStruct, + chainId: CaipChainIdStruct as unknown as Struct< + Infer, + Infer + >, selectedAddress: CaipAccountAddressStruct, }, ); @@ -840,6 +843,7 @@ export const JSXElementStruct: Describe = typedUnion([ SelectorStruct, SelectorOptionStruct, SectionStruct, + AccountSelectorStruct, ]); /** From c3d246fe38515936b31e92434ce0e7119ba676bc Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Thu, 26 Sep 2024 14:28:35 +0200 Subject: [PATCH 3/3] change from `.tsx` to `.ts` --- .../components/form/{AccountSelector.tsx => AccountSelector.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/snaps-sdk/src/jsx/components/form/{AccountSelector.tsx => AccountSelector.ts} (100%) diff --git a/packages/snaps-sdk/src/jsx/components/form/AccountSelector.tsx b/packages/snaps-sdk/src/jsx/components/form/AccountSelector.ts similarity index 100% rename from packages/snaps-sdk/src/jsx/components/form/AccountSelector.tsx rename to packages/snaps-sdk/src/jsx/components/form/AccountSelector.ts