From 30aca146c7a5bace2bbc777660b6e89a5973c26c Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 17 Sep 2024 14:33:18 -0500 Subject: [PATCH 1/9] Add user-defined components how-to --- .../custom-ui/user-defined-components.md | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 snaps/features/custom-ui/user-defined-components.md diff --git a/snaps/features/custom-ui/user-defined-components.md b/snaps/features/custom-ui/user-defined-components.md new file mode 100644 index 0000000000..b0083d75b5 --- /dev/null +++ b/snaps/features/custom-ui/user-defined-components.md @@ -0,0 +1,194 @@ +--- +description: Create your own JSX components to improve readability. +sidebar_position: 5 +--- + +# User-defined components + +When using JSX, you can create your own components by composing [existing components](with-jsx.md), or +other user-defined components. + +## Basic example + +In this first, basic example, the user-defined component is static. It does not accept any props (parameters) and returns the contents of a static home page. + +```jsx title="Home.jsx" +import { Box, Heading, Text } from "@metamask/snaps-sdk/jsx"; + +export const Home = () => { + return ( + + Welcome to my Snap + Hello, world! + + ); +}; +``` + +Once the component is defined, it can be used anywhere in the Snap. For example, to display the home page, you can use the following code: + +```jsx title="index.jsx" +import { Home } from "./Home"; + +export const onHomepage = () => { + return ; +}; +``` + +## Example with props + +Components can be parametrized by passing props. Props are passed to the component as an object and can be accessed using the first parameter of the component's definition function: + +```jsx title="Insight.jsx" +export const Insight = (props) => { + return ( + + +
+ + + {to ?
: None} + + + ); +}; +``` + +In the example above, we see two usages of props: + +- The `Insight` component accepts a `props` parameter, which is an object containing the `from` and `to` addresses. The `from` address is accessed using `props.from`, and the `to` address is accessed using `props.to`, since `props` is just a regular JavaScript object. +- The `Insight` component then uses the built-in `Address` component to display addresses. The `Address` component accepts an `address` prop. When using the `Address` component, we pass props to it by using a notation similar to HTML attributes: `address={props.from}`. + +To use the `Insight` component, you can pass the `from` and `to` addresses as props: + +```jsx title="index.jsx" +import { Insight } from "./Insight"; + +export const onTransaction = () => { + return { content: }; +}; +``` + +Props can be accessed using destructuring as well. This is not specific to JSX, simply a feature of JavaScript: + +```jsx title="Insight.jsx" +export const Insight = ({ from, to }) => { + return ( + + +
+ + + {to ?
: None} + + + ); +}; +``` + +## Return multiple elements + +A JSX expression can only contain a single root element. To return multiple elements, wrap them in a parent element, +like `Box`. In the example above, we wrap the two `Row` elements in a `Box` element. Trying to return multiple elements +without a parent element will result in a syntax error. + +```jsx title="WRONG-Insight.jsx" +export const Insight = ({ from, to }) => { + + // This causes a syntax error + return ( + +
+ + + {to ?
: None} + + ); +}; +``` + +## Return a list + +To return a list of elements, you can use an array. In the example below, the `Accounts` components receives an +array of accounts as props, and uses the array to display a list of accounts using `Array.map`: + +```jsx title="Accounts.jsx" +export const Accounts = ({ accounts }) => { + return ( + + Accounts + {accounts.map((account) => ( + +
+ + ))} + + ); +}; +``` + +To use the `Accounts` component, you can pass an array of accounts as props: + +```jsx title="index.jsx" +import { Accounts } from "./Accounts"; + +export const onHomepage = () => { + return ; +}; +``` + +## Usage with TypeScript + +The `@metamask/snaps-sdk/jsx` package exports a `SnapComponent` type that can be used to define components that are compatible with TypeScript. The `SnapComponent` type is generic: it accepts a `Props` type parameter that will define the shape of the props object. For example: + +```tsx title="Insight.tsx" +import type { SnapComponent } from '@metamask/snaps-sdk/jsx'; +import { Button, Box, Text, Row, Address } from '@metamask/snaps-sdk/jsx'; + +type InsightProps = { + from: string; + to?: string; +}; + +export const Insight: SnapComponent = ({ from, to }) => { + return ( + + +
+ + + {to ?
: None} + + + + ); +}; +``` + +Here are the steps to create user-defined components with TypeScript: + +1. Import the `SnapComponent` type: + ```tsx + import type { SnapComponent } from '@metamask/snaps-sdk/jsx'; + ``` +2. Define a type for the props of your component: + ```tsx + type InsightProps = { + from: string; + to?: string; + }; + `` + +3. Annotate the type of your component: + ```tsx + export const Insight: SnapComponent = ({ from, to }) => { + // ... + }; + ``` + +This will have two effects: + +- It will allow TypeScript to infer the types of the props inside your component. +- It will make sure that the props passed to the component match the expected props. For example, +using the `Insight` component above without the `from` prop, or passing a `number` instead of a +`string` for the `from` prop will result in a type error. \ No newline at end of file From 6b78488593596ac6b636875a7636c53b47dc0edf Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 17 Sep 2024 14:38:32 -0500 Subject: [PATCH 2/9] Properly indent code samples in list --- .../custom-ui/user-defined-components.md | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/snaps/features/custom-ui/user-defined-components.md b/snaps/features/custom-ui/user-defined-components.md index b0083d75b5..367e7ab363 100644 --- a/snaps/features/custom-ui/user-defined-components.md +++ b/snaps/features/custom-ui/user-defined-components.md @@ -168,23 +168,22 @@ export const Insight: SnapComponent = ({ from, to }) => { Here are the steps to create user-defined components with TypeScript: 1. Import the `SnapComponent` type: - ```tsx - import type { SnapComponent } from '@metamask/snaps-sdk/jsx'; - ``` + ```tsx + import type { SnapComponent } from '@metamask/snaps-sdk/jsx'; + ``` 2. Define a type for the props of your component: - ```tsx - type InsightProps = { - from: string; - to?: string; - }; - `` - + ```tsx + type InsightProps = { + from: string; + to?: string; + }; + ``` 3. Annotate the type of your component: - ```tsx - export const Insight: SnapComponent = ({ from, to }) => { - // ... - }; - ``` + ```tsx + export const Insight: SnapComponent = ({ from, to }) => { + // ... + }; + ``` This will have two effects: From 063b97831c1c25e89b4b67fe7cc2e77abd0163b4 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 17 Sep 2024 14:42:58 -0500 Subject: [PATCH 3/9] link from JSX component page to user-defined components page --- snaps/features/custom-ui/with-jsx.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/snaps/features/custom-ui/with-jsx.md b/snaps/features/custom-ui/with-jsx.md index 432f84313a..59ecf5bedf 100644 --- a/snaps/features/custom-ui/with-jsx.md +++ b/snaps/features/custom-ui/with-jsx.md @@ -712,6 +712,10 @@ module.exports.onHomePage = async () => { Text UI example

+## User-defined components + +In addition to the components provided by the SDK, you can also [define your own components](user-defined-components.md). + ## Emojis Text-based components (such as [`Heading`](#heading) and [`Text`](#text)) accept emojis. From 353cc2a05007572fb194043b92ba7f896e568fef Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 17 Sep 2024 14:47:37 -0500 Subject: [PATCH 4/9] Remove Button from Insight tsx component --- snaps/features/custom-ui/user-defined-components.md | 1 - 1 file changed, 1 deletion(-) diff --git a/snaps/features/custom-ui/user-defined-components.md b/snaps/features/custom-ui/user-defined-components.md index 367e7ab363..9ed67adaa0 100644 --- a/snaps/features/custom-ui/user-defined-components.md +++ b/snaps/features/custom-ui/user-defined-components.md @@ -159,7 +159,6 @@ export const Insight: SnapComponent = ({ from, to }) => { {to ?
: None} - ); }; From 4e435bd7e00e876b0876ae63e2ed8e8ff7ded940 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 17 Sep 2024 15:30:15 -0500 Subject: [PATCH 5/9] from > to --- snaps/features/custom-ui/user-defined-components.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snaps/features/custom-ui/user-defined-components.md b/snaps/features/custom-ui/user-defined-components.md index 9ed67adaa0..fc44af2a41 100644 --- a/snaps/features/custom-ui/user-defined-components.md +++ b/snaps/features/custom-ui/user-defined-components.md @@ -157,7 +157,7 @@ export const Insight: SnapComponent = ({ from, to }) => {
- {to ?
: None} + {to ?
: None} ); From dc6207de5f745d3e8d63077390fd79281fe78074 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 17 Sep 2024 15:42:12 -0500 Subject: [PATCH 6/9] Fix Address example --- snaps/features/custom-ui/user-defined-components.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snaps/features/custom-ui/user-defined-components.md b/snaps/features/custom-ui/user-defined-components.md index fc44af2a41..6663118b9b 100644 --- a/snaps/features/custom-ui/user-defined-components.md +++ b/snaps/features/custom-ui/user-defined-components.md @@ -64,8 +64,8 @@ To use the `Insight` component, you can pass the `from` and `to` addresses as pr ```jsx title="index.jsx" import { Insight } from "./Insight"; -export const onTransaction = () => { - return { content: }; +export const onTransaction = ({ transaction }) => { + return { content: }; }; ``` From f596380b73e12ee60ff730e9d50cf5d2b77e0365 Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 17 Sep 2024 15:46:30 -0500 Subject: [PATCH 7/9] use valid ethereum addresses in examples --- snaps/features/custom-ui/user-defined-components.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/snaps/features/custom-ui/user-defined-components.md b/snaps/features/custom-ui/user-defined-components.md index 6663118b9b..dd9badd991 100644 --- a/snaps/features/custom-ui/user-defined-components.md +++ b/snaps/features/custom-ui/user-defined-components.md @@ -133,7 +133,17 @@ To use the `Accounts` component, you can pass an array of accounts as props: import { Accounts } from "./Accounts"; export const onHomepage = () => { - return ; + const accounts = [ + { + name: "Account 1", + address: "0x6827b8f6cc60497d9bf5210d602C0EcaFDF7C405" + }, + { + name: "Account 2", + address: "0x71C7656EC7ab88b098defB751B7401B5f6d8976F" + } + ]; + return ; }; ``` From b7964ab8437f7e8cb471d70be127c3730980951b Mon Sep 17 00:00:00 2001 From: Ziad Saab Date: Tue, 17 Sep 2024 15:51:16 -0500 Subject: [PATCH 8/9] add a section on spreading props --- .../custom-ui/user-defined-components.md | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/snaps/features/custom-ui/user-defined-components.md b/snaps/features/custom-ui/user-defined-components.md index dd9badd991..eb98ca3cff 100644 --- a/snaps/features/custom-ui/user-defined-components.md +++ b/snaps/features/custom-ui/user-defined-components.md @@ -147,6 +147,37 @@ export const onHomepage = () => { }; ``` +## Spread props + +If an object has the same keys and value types as the props of a component, you can spread the +object's properties as props for the component. For example, given the following component: + +```jsx title="Account.jsx" +export const Account = ({ name, address }) => { + return +
+ +}; +``` + +Instead of writing: + +```jsx title="index.jsx" +const myAccount = { + name: "Account 1", + address: "0x6827b8f6cc60497d9bf5210d602C0EcaFDF7C405" +}; + +// ... +return +``` + +You can simply write: + +```jsx +return +``` + ## Usage with TypeScript The `@metamask/snaps-sdk/jsx` package exports a `SnapComponent` type that can be used to define components that are compatible with TypeScript. The `SnapComponent` type is generic: it accepts a `Props` type parameter that will define the shape of the props object. For example: From a870737821d8fecb6cbf17e5c22f769763df3a7c Mon Sep 17 00:00:00 2001 From: Alexandra Tran Date: Wed, 18 Sep 2024 11:21:32 -0700 Subject: [PATCH 9/9] edit content --- .../custom-ui/user-defined-components.md | 85 ++++++++++++------- snaps/features/custom-ui/with-jsx.md | 10 +-- 2 files changed, 60 insertions(+), 35 deletions(-) diff --git a/snaps/features/custom-ui/user-defined-components.md b/snaps/features/custom-ui/user-defined-components.md index eb98ca3cff..b1b6b79c7a 100644 --- a/snaps/features/custom-ui/user-defined-components.md +++ b/snaps/features/custom-ui/user-defined-components.md @@ -5,12 +5,13 @@ sidebar_position: 5 # User-defined components -When using JSX, you can create your own components by composing [existing components](with-jsx.md), or -other user-defined components. +When using [Custom UI with JSX](with-jsx.md), you can create your own components by composing +existing components or other user-defined components. ## Basic example -In this first, basic example, the user-defined component is static. It does not accept any props (parameters) and returns the contents of a static home page. +In this first, basic example, the user-defined component is static. +It does not accept any props (parameters) and returns the contents of a static home page. ```jsx title="Home.jsx" import { Box, Heading, Text } from "@metamask/snaps-sdk/jsx"; @@ -25,7 +26,8 @@ export const Home = () => { }; ``` -Once the component is defined, it can be used anywhere in the Snap. For example, to display the home page, you can use the following code: +Once the component is defined, you can use it anywhere in the Snap. +For example, to display the home page, you can use the following code: ```jsx title="index.jsx" import { Home } from "./Home"; @@ -37,7 +39,9 @@ export const onHomepage = () => { ## Example with props -Components can be parametrized by passing props. Props are passed to the component as an object and can be accessed using the first parameter of the component's definition function: +You can parameterize components by passing props. +Props are passed to the component as an object and can be accessed using the first parameter of the +component's definition function: ```jsx title="Insight.jsx" export const Insight = (props) => { @@ -54,10 +58,16 @@ export const Insight = (props) => { }; ``` -In the example above, we see two usages of props: +This example contains two usages of props: -- The `Insight` component accepts a `props` parameter, which is an object containing the `from` and `to` addresses. The `from` address is accessed using `props.from`, and the `to` address is accessed using `props.to`, since `props` is just a regular JavaScript object. -- The `Insight` component then uses the built-in `Address` component to display addresses. The `Address` component accepts an `address` prop. When using the `Address` component, we pass props to it by using a notation similar to HTML attributes: `address={props.from}`. +- The `Insight` component accepts a `props` parameter, which is an object containing the `from` and + `to` addresses. + The `from` address is accessed using `props.from`, and the `to` address is accessed using + `props.to`, since `props` is just a regular JavaScript object. +- The `Insight` component then uses the built-in `Address` component to display addresses. + The `Address` component accepts an `address` prop. + When using the `Address` component, you can pass props to it using a notation similar to HTML + attributes: `address={props.from}`. To use the `Insight` component, you can pass the `from` and `to` addresses as props: @@ -69,7 +79,8 @@ export const onTransaction = ({ transaction }) => { }; ``` -Props can be accessed using destructuring as well. This is not specific to JSX, simply a feature of JavaScript: +You can also access props using destructuring. +This is not specific to JSX, but a feature of JavaScript: ```jsx title="Insight.jsx" export const Insight = ({ from, to }) => { @@ -88,9 +99,10 @@ export const Insight = ({ from, to }) => { ## Return multiple elements -A JSX expression can only contain a single root element. To return multiple elements, wrap them in a parent element, -like `Box`. In the example above, we wrap the two `Row` elements in a `Box` element. Trying to return multiple elements -without a parent element will result in a syntax error. +A JSX expression can only contain a single root element. +To return multiple elements, wrap them in a parent element, such as `Box`. +In the previous example, the two `Row` elements are wrapped in a `Box` element. +Trying to return multiple elements without a parent element results in a syntax error: ```jsx title="WRONG-Insight.jsx" export const Insight = ({ from, to }) => { @@ -109,8 +121,9 @@ export const Insight = ({ from, to }) => { ## Return a list -To return a list of elements, you can use an array. In the example below, the `Accounts` components receives an -array of accounts as props, and uses the array to display a list of accounts using `Array.map`: +To return a list of elements, you can use an array. +In the following example, the `Accounts` components receives an array of accounts as props, and uses +the array to display a list of accounts using `Array.map`: ```jsx title="Accounts.jsx" export const Accounts = ({ accounts }) => { @@ -150,7 +163,8 @@ export const onHomepage = () => { ## Spread props If an object has the same keys and value types as the props of a component, you can spread the -object's properties as props for the component. For example, given the following component: +object's properties as props for the component. +For example, given the following component: ```jsx title="Account.jsx" export const Account = ({ name, address }) => { @@ -172,19 +186,23 @@ const myAccount = { return ``` -You can simply write: +You can write: ```jsx return ``` -## Usage with TypeScript +## Use with TypeScript -The `@metamask/snaps-sdk/jsx` package exports a `SnapComponent` type that can be used to define components that are compatible with TypeScript. The `SnapComponent` type is generic: it accepts a `Props` type parameter that will define the shape of the props object. For example: +The `@metamask/snaps-sdk/jsx` package exports a `SnapComponent` type that you can use to define +components that are compatible with TypeScript. +The `SnapComponent` type is generic: it accepts a `Props` type parameter that defines the shape of +the props object. +For example: ```tsx title="Insight.tsx" -import type { SnapComponent } from '@metamask/snaps-sdk/jsx'; -import { Button, Box, Text, Row, Address } from '@metamask/snaps-sdk/jsx'; +import type { SnapComponent } from "@metamask/snaps-sdk/jsx"; +import { Button, Box, Text, Row, Address } from "@metamask/snaps-sdk/jsx"; type InsightProps = { from: string; @@ -205,29 +223,36 @@ export const Insight: SnapComponent = ({ from, to }) => { }; ``` -Here are the steps to create user-defined components with TypeScript: +Use the following steps to create user-defined components with TypeScript: 1. Import the `SnapComponent` type: + ```tsx - import type { SnapComponent } from '@metamask/snaps-sdk/jsx'; + import type { SnapComponent } from "@metamask/snaps-sdk/jsx"; ``` -2. Define a type for the props of your component: + +2. Define a type for the props of your component. + For example: + ```tsx type InsightProps = { from: string; to?: string; }; ``` -3. Annotate the type of your component: + +3. Annotate the type of your component. + For example: + ```tsx export const Insight: SnapComponent = ({ from, to }) => { // ... }; ``` -This will have two effects: - -- It will allow TypeScript to infer the types of the props inside your component. -- It will make sure that the props passed to the component match the expected props. For example, -using the `Insight` component above without the `from` prop, or passing a `number` instead of a -`string` for the `from` prop will result in a type error. \ No newline at end of file + This has two effects: + + - It allows TypeScript to infer the types of the props inside your component. + - It ensures that the props passed to the component match the expected props. + In this example, using the `Insight` component without the `from` prop, or passing a `number` + instead of a `string` for the `from` prop results in a type error. diff --git a/snaps/features/custom-ui/with-jsx.md b/snaps/features/custom-ui/with-jsx.md index 59ecf5bedf..aec606c6b8 100644 --- a/snaps/features/custom-ui/with-jsx.md +++ b/snaps/features/custom-ui/with-jsx.md @@ -712,11 +712,7 @@ module.exports.onHomePage = async () => { Text UI example

-## User-defined components - -In addition to the components provided by the SDK, you can also [define your own components](user-defined-components.md). - -## Emojis +### Emojis Text-based components (such as [`Heading`](#heading) and [`Text`](#text)) accept emojis. @@ -742,3 +738,7 @@ await snap.request({

Emojis UI example

+ +## User-defined components + +In addition to the components provided by the SDK, you can [define your own components](user-defined-components.md).