Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add interactive UI documentation #1166

Merged
merged 30 commits into from Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0c37029
Update snaps-ui and snaps-types references
ziad-saab Feb 22, 2024
48f3831
Add interactive elements to Custom UI features
ziad-saab Feb 22, 2024
9cc4d6f
Add interactive UI methods to Snaps API reference
ziad-saab Feb 23, 2024
a47b190
whatever
ziad-saab Feb 23, 2024
730a25c
update interactive-ui filename
ziad-saab Feb 23, 2024
d0e8f25
add onUserInput entry point
ziad-saab Feb 23, 2024
fbeb36b
add flask only label to interactive UI
ziad-saab Feb 23, 2024
89f0f60
add feature card for interactive ui
ziad-saab Feb 23, 2024
8550810
add interactive ui content page
ziad-saab Feb 23, 2024
2a02645
Merge remote-tracking branch 'origin/main' into zs/interactive-ui
ziad-saab Feb 23, 2024
94fc470
Merge remote-tracking branch 'origin/main' into zs/interactive-ui
ziad-saab Feb 24, 2024
4e50084
Add signature insights documentation (#1103)
ziad-saab Jan 25, 2024
76cc61b
fix broken links
ziad-saab Feb 24, 2024
3157b27
Merge remote-tracking branch 'origin/main' into zs/interactive-ui
ziad-saab Mar 19, 2024
60cf7ce
remove signature stuff
ziad-saab Mar 19, 2024
a028e9d
Merge remote-tracking branch 'origin/main' into zs/interactive-ui
ziad-saab Mar 20, 2024
6cf3d79
add places where interactive UI can be used
ziad-saab Mar 20, 2024
a33020a
Merge branch 'main' into zs/interactive-ui
alexandratran Mar 20, 2024
1435f06
Edit content
alexandratran Mar 20, 2024
7521077
Merge branch 'main' into zs/interactive-ui
alexandratran Mar 20, 2024
74510e6
fix link
alexandratran Mar 20, 2024
be75d47
add interactive ui screenshots
alexandratran Mar 21, 2024
078d7ef
Merge remote-tracking branch 'origin/main' into zs/interactive-ui
ziad-saab Mar 21, 2024
243b916
type > buttonType
ziad-saab Mar 21, 2024
d4377f4
Re-add onUserInput entry point
ziad-saab Mar 21, 2024
6f2f600
rearrange interactive ui methods and minor edits
alexandratran Mar 21, 2024
2fe92c1
Merge remote-tracking branch 'origin/main' into zs/interactive-ui
ziad-saab Mar 22, 2024
d2f5ba5
fix typo
ziad-saab Mar 22, 2024
9e5c99c
Merge branch 'main' into zs/interactive-ui
alexandratran Mar 28, 2024
bd43167
fix links
alexandratran Mar 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file added snaps/assets/custom-ui-button.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added snaps/assets/custom-ui-form.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
199 changes: 180 additions & 19 deletions snaps/features/custom-ui.md → snaps/features/custom-ui/index.md
Expand Up @@ -5,13 +5,13 @@ sidebar_position: 1

# Custom UI

You can display custom user interface (UI) components using the
You can display custom user interface (UI) components using the
[`@metamask/snaps-sdk`](https://github.com/MetaMask/snaps/tree/main/packages/snaps-sdk) module with
the following methods and entry points:

- [`snap_dialog`](../reference/snaps-api.md#snap_dialog)
- [`onTransaction`](../reference/entry-points.md#ontransaction)
- [`onHomePage`](../reference/entry-points.md#onhomepage)
- [`snap_dialog`](../../reference/snaps-api.md#snap_dialog)
- [`onTransaction`](../../reference/entry-points.md#ontransaction)
- [`onHomePage`](../../reference/entry-points.md#onhomepage)

To use custom UI, first install [`@metamask/snaps-sdk`](https://github.com/MetaMask/snaps/tree/main/packages/snaps-sdk)
using the following command:
Expand All @@ -22,7 +22,7 @@ yarn add @metamask/snaps-sdk

Then, whenever you're required to return a custom UI component, import the components from the
SDK and build your UI with them.
For example, to display a [`panel`](#panel) using [`snap_dialog`](../reference/snaps-api.md#snap_dialog):
For example, to display a [`panel`](#panel) using [`snap_dialog`](../../reference/snaps-api.md#snap_dialog):

```javascript title="index.js"
import { panel, heading, text } from "@metamask/snaps-sdk";
Expand Down Expand Up @@ -68,13 +68,65 @@ await snap.request({

<div class="row">
<div class="column">
<img src={require("../assets/custom-ui-address.png").default} alt="Address UI example" width="450px" style={{border: '1px solid #DCDCDC'}} />
<img src={require("../../assets/custom-ui-address.png").default} alt="Address UI example" width="450px" style={{border: '1px solid #DCDCDC'}} />
</div>
<div class="column">
<img src={require("../assets/custom-ui-address-tooltip.png").default} alt="Address tooltip UI example" width="450px" style={{border: '1px solid #DCDCDC'}} />
<img src={require("../../assets/custom-ui-address-tooltip.png").default} alt="Address tooltip UI example" width="450px" style={{border: '1px solid #DCDCDC'}} />
</div>
</div>

### `button`

:::flaskOnly
:::

Outputs a button that the user can select.
For use in [interactive UI](interactive-ui.md).

#### Parameters

An object containing:

- `value`: `string` - The text of the button.
- `buttonType`: `string` - (Optional) Possible values are `button` or `submit`.
The default is `button`.
- `name`: `string` - (Optional) The name that will be sent to [`onUserInput`](../../reference/entry-points.md#onuserinput)
when a user selects the button.
- `variant` - (Optional) Determines the appearance of the button.
Possible values are `primary` or `secondary`.
The default is `primary`.

#### Example

```javascript
import { button, panel, heading } from "@metamask/snaps-sdk";

const interfaceId = await snap.request({
method: "snap_createInterface",
params: {
ui: panel([
heading("Interactive interface"),
button({
value: "Click me",
name: "interactive-button",
}),
]),
},
});

await snap.request({
method: "snap_dialog",
params: {
type: "Alert",
id: interfaceId,
},
});
```

<p align="center">
<img src={require("../../assets/custom-ui-button.png").default} alt="Button UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
</p>

### `copyable`

Outputs a read-only text field with a copy-to-clipboard shortcut.
Expand All @@ -97,7 +149,7 @@ await snap.request({
```

<p align="center">
<img src={require("../assets/custom-ui-copyable.png").default} alt="Copyable UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
<img src={require("../../assets/custom-ui-copyable.png").default} alt="Copyable UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
</p>

### `divider`
Expand All @@ -122,7 +174,59 @@ module.exports.onHomePage = async () => {
```

<p align="center">
<img src={require("../assets/custom-ui-divider.png").default} alt="Divider UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
<img src={require("../../assets/custom-ui-divider.png").default} alt="Divider UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
</p>

### `form`

:::flaskOnly
:::

Outputs a form for use in [interactive UI](interactive-ui.md).

#### Parameters

An object containing:

- `name`: `string` - The name that will be sent to [`onUserInput`](../../reference/entry-points.md#onuserinput)
when a user interacts with the form.
- `children`: `array` - An array of [`input`](#input) or [`button`](#button) components.

#### Example

```js
import { input, button, form } from "@metamask/snaps-sdk";

const interfaceId = await snap.request({
method: "snap_createInterface",
params: {
ui: form({
name: "form-to-fill",
children: [
input({
name: "user-name",
placeholder: "Your name",
}),
button({
value: "Submit",
buttonType: "submit",
}),
],
}),
},
});

await snap.request({
method: "snap_dialog",
params: {
type: "Alert",
id: interfaceId,
},
});
```

<p align="center">
<img src={require("../../assets/custom-ui-form.png").default} alt="Form UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
</p>

### `heading`
Expand All @@ -147,7 +251,7 @@ module.exports.onHomePage = async () => {
```

<p align="center">
<img src={require("../assets/custom-ui-heading.png").default} alt="Divider UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
<img src={require("../../assets/custom-ui-heading.png").default} alt="Divider UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
</p>

### `image`
Expand All @@ -163,7 +267,7 @@ The SVG is rendered within an `<img>` tag, which prevents JavaScript or interact
being supported.

:::note
To disable image support, set the [`features.images`](../reference/cli/options.md#featuresimages)
To disable image support, set the [`features.images`](../../reference/cli/options.md#featuresimages)
configuration option to `false`.
The default is `true`.
:::
Expand All @@ -187,7 +291,64 @@ module.exports.onHomePage = async () => {
```

<p align="center">
<img src={require("../assets/custom-ui-image.png").default} alt="Divider UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
<img src={require("../../assets/custom-ui-image.png").default} alt="Divider UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
</p>

### `input`

:::flaskOnly
:::

Outputs an input component for use in [interactive UI](interactive-ui.md).

#### Parameters

An object containing:

- `name`: `string` - The name that will be used as a key to the event sent to
[`onUserInput`](../../reference/entry-points.md#onuserinput) when the containing form is submitted.
- `inputType`: `string` - (Optional) Type of input.
Possible values are `text`, `number`, or `password`.
The default is `text`.
- `placeholder`: `string` - (Optional) The text displayed when the input is empty.
- `label`: `string` (Optional) The text displayed alongside the input to label it.
Montoya marked this conversation as resolved.
Show resolved Hide resolved
- `value`: `string` (Optional) The default value of the input.

#### Example

```js
import { input, form } from "@metamask/snaps-sdk";

const interfaceId = await snap.request({
method: "snap_createInterface",
params: {
ui: form({
name: "form-to-fill",
children: [
input({
name: "user-name",
placeholder: "Your name",
}),
button({
value: "Submit",
buttonType: "submit",
}),
],
}),
},
});

await snap.request({
method: "snap_dialog",
params: {
type: "Alert",
id: interfaceId,
},
});
```

<p align="center">
<img src={require("../../assets/custom-ui-form.png").default} alt="Form UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
</p>

:::note
Expand Down Expand Up @@ -222,7 +383,7 @@ module.exports.onTransaction = async ({ transaction }) => {
```

<p align="center">
<img src={require("../assets/custom-ui-panel.png").default} alt="Panel UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
<img src={require("../../assets/custom-ui-panel.png").default} alt="Panel UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
</p>

### `row`
Expand All @@ -249,7 +410,7 @@ await snap.request({
```

<p align="center">
<img src={require("../assets/custom-ui-row.png").default} alt="Row UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
<img src={require("../../assets/custom-ui-row.png").default} alt="Row UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
</p>

### `spinner`
Expand All @@ -274,7 +435,7 @@ await snap.request({
```

<p align="center">
<img src={require("../assets/custom-ui-spinner.gif").default} alt="Spinner UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
<img src={require("../../assets/custom-ui-spinner.gif").default} alt="Spinner UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
</p>

### `text`
Expand All @@ -298,7 +459,7 @@ module.exports.onHomePage = async () => {
```

<p align="center">
<img src={require("../assets/custom-ui-heading.png").default} alt="Spinner UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
<img src={require("../../assets/custom-ui-heading.png").default} alt="Spinner UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
</p>

## Markdown
Expand All @@ -323,7 +484,7 @@ await snap.request({
```

<p align="center">
<img src={require("../assets/custom-ui-markdown.png").default} alt="Markdown UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
<img src={require("../../assets/custom-ui-markdown.png").default} alt="Markdown UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
</p>

## Links
Expand All @@ -348,7 +509,7 @@ module.exports.onHomePage = async () => {
```

<p align="center">
<img src={require("../assets/custom-ui-links.png").default} alt="Spinner UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
<img src={require("../../assets/custom-ui-links.png").default} alt="Spinner UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
</p>

## Emojis
Expand All @@ -373,7 +534,7 @@ await snap.request({
```

<p align="center">
<img src={require("../assets/custom-ui-emojis.png").default} alt="Emojis UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
<img src={require("../../assets/custom-ui-emojis.png").default} alt="Emojis UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
</p>

## Examples
Expand Down
67 changes: 67 additions & 0 deletions snaps/features/custom-ui/interactive-ui.md
@@ -0,0 +1,67 @@
---
description: Display and update interactive user interfaces.
sidebar_position: 1
sidebar_custom_props:
flask_only: true
---

# Interactive UI

:::flaskOnly
:::

You can display interactive user interface (UI) components.
Interactive UI is an extension of [custom UI](index.md).
It allows interfaces returned from [`snap_dialog`](../../reference/snaps-api.md#snap_dialog),
[`onTransaction`](../../reference/entry-points.md#ontransaction), and
[`onHomePage`](../../reference/entry-points.md#onhomepage) to respond to user input.

The following interactive UI components are available:

- [`button`](index.md#button)
- [`form`](index.md#form)
- [`input`](index.md#input)

## Create an interactive interface

Create an interactive interface using the
[`snap_createInterface`](../../reference/snaps-api.md#snap_createinterface) method.
This method returns the ID of the created interface.
You can pass this ID to [`snap_dialog`](../../reference/snaps-api.md#snap_dialog), returned from
[`onTransaction`](../../reference/entry-points.md#ontransaction), or from
[`onHomePage`](../../reference/entry-points.md#onhomepage).

If you need to [update the interface](#update-an-interactive-interface) or
[get its state](#get-an-interactive-interfaces-state) at a future time, you should store its ID in
the Snap's storage.

## Update an interactive interface

To update an interactive interface that is still active, use the
[`snap_updateInterface`](../../reference/snaps-api.md#snap_updateinterface) method.
Pass the ID of the interface to be updated, and the new UI.

Updating an interface can be done as part of the
[`onUserInput`](../../reference/entry-points.md#onuserinput) entry point or as part of an
asynchronous process.

The following is an example flow:

1. The user activates an interactive interface to send Bitcoin funds to an address.
The initial interface contains an address input, an amount input, and a **Send funds** button.
2. The user fills the fields, and selects the **Send funds** button.
3. `onUserInput` is called, and the logic detects that the **Send funds** button was selected.
4. `snap_updateInterface` is called, replacing the **Send funds** button with a [`spinner`](index.md#spinner).
5. Custom logic sends the funds.
6. `snap_updateInterface` is called again, replacing the whole UI with a success message.

## Get an interactive interface's state

At any point you can retrieve an interactive interface's state.
To do this, call the [`snap_getInterfaceState`](../../reference/snaps-api.md#snap_getinterfacestate)
method with the ID of the interface.

## Example

See the [`@metamask/interactive-ui-example-snap`](https://github.com/MetaMask/snaps/tree/main/packages/examples/packages/interactive-ui)
package for a full example of implementing interactive UI.
9 changes: 9 additions & 0 deletions snaps/how-to/request-permissions.md
Expand Up @@ -27,6 +27,15 @@ following to the manifest file:
}
```

:::note
All Snaps API methods except the following interactive UI methods require requesting permission in
the manifest file:

- [`snap_createInterface`](../reference/snaps-api.md#snap_createinterface)
- [`snap_getInterfaceState`](../reference/snaps-api.md#snap_getinterfacestate)
- [`snap_updateInterface`](../reference/snaps-api.md#snap_updateInterface)
:::

### Endowments

Endowments are a type of permission.
Expand Down