From 50398a0b84bb3a069655755c78e44ff26c66cfd5 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 17 Dec 2024 14:29:15 +0100 Subject: [PATCH 1/6] chore: Bump Snaps packages --- builds.yml | 8 +++--- package.json | 14 ++++----- yarn.lock | 80 ++++++++++++++++++++++++++-------------------------- 3 files changed, 51 insertions(+), 51 deletions(-) diff --git a/builds.yml b/builds.yml index e8c7f5a28d93..43cf02d3c8e5 100644 --- a/builds.yml +++ b/builds.yml @@ -27,7 +27,7 @@ buildTypes: - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - REJECT_INVALID_SNAPS_PLATFORM_VERSION: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.10.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.11.0/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management # Main build uses the default browser manifest manifestOverrides: false @@ -48,7 +48,7 @@ buildTypes: - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - REJECT_INVALID_SNAPS_PLATFORM_VERSION: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.10.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.11.0/index.html - ACCOUNT_SNAPS_DIRECTORY_URL: https://snaps.metamask.io/account-management # Modifies how the version is displayed. # eg. instead of 10.25.0 -> 10.25.0-beta.2 @@ -71,7 +71,7 @@ buildTypes: - ALLOW_LOCAL_SNAPS: true - REQUIRE_SNAPS_ALLOWLIST: false - REJECT_INVALID_SNAPS_PLATFORM_VERSION: false - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.10.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.11.0/index.html - SUPPORT_LINK: https://support.metamask.io/ - SUPPORT_REQUEST_LINK: https://support.metamask.io/ - INFURA_ENV_KEY_REF: INFURA_FLASK_PROJECT_ID @@ -95,7 +95,7 @@ buildTypes: - ALLOW_LOCAL_SNAPS: false - REQUIRE_SNAPS_ALLOWLIST: true - REJECT_INVALID_SNAPS_PLATFORM_VERSION: true - - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.10.0/index.html + - IFRAME_EXECUTION_ENVIRONMENT_URL: https://execution.metamask.io/iframe/6.11.0/index.html - MMI_CONFIGURATION_SERVICE_URL: https://configuration.metamask-institutional.io/v2/configuration/default - SUPPORT_LINK: https://support.metamask-institutional.io - SUPPORT_REQUEST_LINK: https://support.metamask-institutional.io diff --git a/package.json b/package.json index 1b09e5a96d7e..4a7632d2d2d3 100644 --- a/package.json +++ b/package.json @@ -225,7 +225,7 @@ "semver@7.3.8": "^7.5.4", "@trezor/schema-utils@npm:1.0.2": "patch:@trezor/schema-utils@npm%3A1.0.2#~/.yarn/patches/@trezor-schema-utils-npm-1.0.2-7dd48689b2.patch", "lavamoat-core@npm:^15.1.1": "patch:lavamoat-core@npm%3A15.1.1#~/.yarn/patches/lavamoat-core-npm-15.1.1-51fbe39988.patch", - "@metamask/snaps-sdk": "^6.13.0", + "@metamask/snaps-sdk": "^6.14.0", "@swc/types@0.1.5": "^0.1.6", "@babel/core": "patch:@babel/core@npm%3A7.25.9#~/.yarn/patches/@babel-core-npm-7.25.9-4ae3bff7f3.patch", "@babel/runtime": "patch:@babel/runtime@npm%3A7.25.9#~/.yarn/patches/@babel-runtime-npm-7.25.9-fe8c62510a.patch", @@ -331,7 +331,7 @@ "@metamask/polling-controller": "^12.0.1", "@metamask/post-message-stream": "^8.0.0", "@metamask/ppom-validator": "0.36.0", - "@metamask/preinstalled-example-snap": "^0.2.0", + "@metamask/preinstalled-example-snap": "^0.3.0", "@metamask/profile-sync-controller": "^3.1.1", "@metamask/providers": "^18.2.0", "@metamask/queued-request-controller": "^7.0.1", @@ -343,11 +343,11 @@ "@metamask/selected-network-controller": "^19.0.0", "@metamask/signature-controller": "^23.1.0", "@metamask/smart-transactions-controller": "^16.0.0", - "@metamask/snaps-controllers": "^9.15.0", - "@metamask/snaps-execution-environments": "^6.10.0", - "@metamask/snaps-rpc-methods": "^11.7.0", - "@metamask/snaps-sdk": "^6.13.0", - "@metamask/snaps-utils": "^8.6.1", + "@metamask/snaps-controllers": "^9.16.0", + "@metamask/snaps-execution-environments": "^6.11.0", + "@metamask/snaps-rpc-methods": "^11.8.0", + "@metamask/snaps-sdk": "^6.14.0", + "@metamask/snaps-utils": "^8.7.0", "@metamask/solana-wallet-snap": "^1.0.4", "@metamask/transaction-controller": "^42.0.0", "@metamask/user-operation-controller": "^21.0.0", diff --git a/yarn.lock b/yarn.lock index 530e073f615d..e00947e58386 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6063,12 +6063,12 @@ __metadata: languageName: node linkType: hard -"@metamask/preinstalled-example-snap@npm:^0.2.0": - version: 0.2.0 - resolution: "@metamask/preinstalled-example-snap@npm:0.2.0" +"@metamask/preinstalled-example-snap@npm:^0.3.0": + version: 0.3.0 + resolution: "@metamask/preinstalled-example-snap@npm:0.3.0" dependencies: - "@metamask/snaps-sdk": "npm:^6.9.0" - checksum: 10/f8ad6f42c9bd7ce3b7fc9b45eecda6191320ff762b48c482ba4944a6d7a228682b833c15e56058f26ac7bb10417dfe9de340af1c8eb9bbe5dc03c665426ccb13 + "@metamask/snaps-sdk": "npm:^6.14.0" + checksum: 10/add8f89c1b7327bc90486d868a9d4b7eff426ef98a5a96235fc6fdce4710c6d17842636ccd02db6638d061ce2b16939c6fe1f06e69cdde8bde2f6026c7b82df5 languageName: node linkType: hard @@ -6265,9 +6265,9 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-controllers@npm:^9.10.0, @metamask/snaps-controllers@npm:^9.15.0": - version: 9.15.0 - resolution: "@metamask/snaps-controllers@npm:9.15.0" +"@metamask/snaps-controllers@npm:^9.10.0, @metamask/snaps-controllers@npm:^9.16.0": + version: 9.16.0 + resolution: "@metamask/snaps-controllers@npm:9.16.0" dependencies: "@metamask/approval-controller": "npm:^7.1.1" "@metamask/base-controller": "npm:^7.0.2" @@ -6280,9 +6280,9 @@ __metadata: "@metamask/post-message-stream": "npm:^8.1.1" "@metamask/rpc-errors": "npm:^7.0.1" "@metamask/snaps-registry": "npm:^3.2.2" - "@metamask/snaps-rpc-methods": "npm:^11.7.0" - "@metamask/snaps-sdk": "npm:^6.13.0" - "@metamask/snaps-utils": "npm:^8.6.1" + "@metamask/snaps-rpc-methods": "npm:^11.8.0" + "@metamask/snaps-sdk": "npm:^6.14.0" + "@metamask/snaps-utils": "npm:^8.7.0" "@metamask/utils": "npm:^10.0.0" "@xstate/fsm": "npm:^2.0.0" browserify-zlib: "npm:^0.2.0" @@ -6296,30 +6296,30 @@ __metadata: semver: "npm:^7.5.4" tar-stream: "npm:^3.1.7" peerDependencies: - "@metamask/snaps-execution-environments": ^6.10.0 + "@metamask/snaps-execution-environments": ^6.11.0 peerDependenciesMeta: "@metamask/snaps-execution-environments": optional: true - checksum: 10/dd849398c4deefbca55b3b4a5e3fe885c45012cd8132bb83367d024c0c2dc99b13be036aa84049d5a1ba1431f9fd66897623f3961a34ebbbf70fe7bce4db322e + checksum: 10/f0a9efaad8fac2aa833edd5df6a4929a84de31f3e11457d407f39793c9ecd3b94eff543135729691b125c32f4290183375ae6416bc04e4aad31466517727af4f languageName: node linkType: hard -"@metamask/snaps-execution-environments@npm:^6.10.0": - version: 6.10.0 - resolution: "@metamask/snaps-execution-environments@npm:6.10.0" +"@metamask/snaps-execution-environments@npm:^6.11.0": + version: 6.11.0 + resolution: "@metamask/snaps-execution-environments@npm:6.11.0" dependencies: "@metamask/json-rpc-engine": "npm:^10.0.1" "@metamask/object-multiplex": "npm:^2.0.0" "@metamask/post-message-stream": "npm:^8.1.1" "@metamask/providers": "npm:^18.1.1" "@metamask/rpc-errors": "npm:^7.0.1" - "@metamask/snaps-sdk": "npm:^6.11.0" - "@metamask/snaps-utils": "npm:^8.6.0" + "@metamask/snaps-sdk": "npm:^6.14.0" + "@metamask/snaps-utils": "npm:^8.7.0" "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^10.0.0" nanoid: "npm:^3.1.31" readable-stream: "npm:^3.6.2" - checksum: 10/a881696ec942f268d7485869fcb8c6bc0c278319bbfaf7e5c6099e86278c7f59049595f00ecfc27511d0106b5ad2f7621f734c7b17f088b835e38e638d80db01 + checksum: 10/3fc46e1b1d7e11996ce8c3694738d1cdab9b5d6c129a45e691b98f0d753e044869c3d0471729cba9e120bb2ff7ebd8e9aa644e608792903e74eee61213509b08 languageName: node linkType: hard @@ -6335,38 +6335,38 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-rpc-methods@npm:^11.7.0": - version: 11.7.0 - resolution: "@metamask/snaps-rpc-methods@npm:11.7.0" +"@metamask/snaps-rpc-methods@npm:^11.8.0": + version: 11.8.0 + resolution: "@metamask/snaps-rpc-methods@npm:11.8.0" dependencies: "@metamask/key-tree": "npm:^10.0.1" "@metamask/permission-controller": "npm:^11.0.3" "@metamask/rpc-errors": "npm:^7.0.1" - "@metamask/snaps-sdk": "npm:^6.13.0" - "@metamask/snaps-utils": "npm:^8.6.1" + "@metamask/snaps-sdk": "npm:^6.14.0" + "@metamask/snaps-utils": "npm:^8.7.0" "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^10.0.0" "@noble/hashes": "npm:^1.3.1" - checksum: 10/92e4131d15b8dd68a29bd845e6c795aab8c3299048eaff2c3970db78a5eb476d8841f6a612b42e878812bb0757f2126287581f4e12259846851f02d6e6d836f5 + checksum: 10/a84c3648195efaeaeb021bd86c8a90e3f555236a8804cb2191778dddcf2acf7ea23ebc30f4670f6669dc881736c28f387b6ca2fc61050393411ee947a86cd47b languageName: node linkType: hard -"@metamask/snaps-sdk@npm:^6.13.0": - version: 6.13.0 - resolution: "@metamask/snaps-sdk@npm:6.13.0" +"@metamask/snaps-sdk@npm:^6.14.0": + version: 6.14.0 + resolution: "@metamask/snaps-sdk@npm:6.14.0" dependencies: "@metamask/key-tree": "npm:^10.0.1" "@metamask/providers": "npm:^18.1.1" "@metamask/rpc-errors": "npm:^7.0.1" "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^10.0.0" - checksum: 10/115c738cb140810856ded055ac92a538c011adbd6a5f32a4e1fde42dcbd162c7eac182aab904de6b65af99b9520995d768627bd7f460da11d0aa359700e05b04 + checksum: 10/dafe8618418c607c5d962bbcf675324651254631791a257898f4a939c58a8b2f56a743dcff534aa7889662d5fb1a4dd1048558f4b025404e0dcd507ff5a5e89a languageName: node linkType: hard -"@metamask/snaps-utils@npm:^8.3.0, @metamask/snaps-utils@npm:^8.6.0, @metamask/snaps-utils@npm:^8.6.1": - version: 8.6.1 - resolution: "@metamask/snaps-utils@npm:8.6.1" +"@metamask/snaps-utils@npm:^8.3.0, @metamask/snaps-utils@npm:^8.7.0": + version: 8.7.0 + resolution: "@metamask/snaps-utils@npm:8.7.0" dependencies: "@babel/core": "npm:^7.23.2" "@babel/types": "npm:^7.23.0" @@ -6376,7 +6376,7 @@ __metadata: "@metamask/rpc-errors": "npm:^7.0.1" "@metamask/slip44": "npm:^4.0.0" "@metamask/snaps-registry": "npm:^3.2.2" - "@metamask/snaps-sdk": "npm:^6.13.0" + "@metamask/snaps-sdk": "npm:^6.14.0" "@metamask/superstruct": "npm:^3.1.0" "@metamask/utils": "npm:^10.0.0" "@noble/hashes": "npm:^1.3.1" @@ -6391,7 +6391,7 @@ __metadata: semver: "npm:^7.5.4" ses: "npm:^1.1.0" validate-npm-package-name: "npm:^5.0.0" - checksum: 10/d58e276b2849662e764a4ce5f45a03df361a5c65e9c09814e41b723407dffbd3a215626f5d9a8c4705a0cad73b702e10d76201174b44f92fbc68fdf59fb24d5d + checksum: 10/0681878e29c010853b610ed99569044feaa37b4cc92bafdba28b1eec68694d7779833fb4262a05a4d18182c6931d258531ed628e422d7c96567b61d1b710a95d languageName: node linkType: hard @@ -26663,7 +26663,7 @@ __metadata: "@metamask/post-message-stream": "npm:^8.0.0" "@metamask/ppom-validator": "npm:0.36.0" "@metamask/preferences-controller": "npm:^15.0.1" - "@metamask/preinstalled-example-snap": "npm:^0.2.0" + "@metamask/preinstalled-example-snap": "npm:^0.3.0" "@metamask/profile-sync-controller": "npm:^3.1.1" "@metamask/providers": "npm:^18.2.0" "@metamask/queued-request-controller": "npm:^7.0.1" @@ -26675,11 +26675,11 @@ __metadata: "@metamask/selected-network-controller": "npm:^19.0.0" "@metamask/signature-controller": "npm:^23.1.0" "@metamask/smart-transactions-controller": "npm:^16.0.0" - "@metamask/snaps-controllers": "npm:^9.15.0" - "@metamask/snaps-execution-environments": "npm:^6.10.0" - "@metamask/snaps-rpc-methods": "npm:^11.7.0" - "@metamask/snaps-sdk": "npm:^6.13.0" - "@metamask/snaps-utils": "npm:^8.6.1" + "@metamask/snaps-controllers": "npm:^9.16.0" + "@metamask/snaps-execution-environments": "npm:^6.11.0" + "@metamask/snaps-rpc-methods": "npm:^11.8.0" + "@metamask/snaps-sdk": "npm:^6.14.0" + "@metamask/snaps-utils": "npm:^8.7.0" "@metamask/solana-wallet-snap": "npm:^1.0.4" "@metamask/test-bundler": "npm:^1.0.0" "@metamask/test-dapp": "npm:8.13.0" From 26da01df374e3dff0a2329d66cad988f5fc2f142 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 17 Dec 2024 14:50:32 +0100 Subject: [PATCH 2/6] Add font weight and size props --- .../snaps/snap-ui-button/snap-ui-button.tsx | 3 ++ .../snap-ui-renderer/components/button.ts | 3 ++ .../snaps/snap-ui-renderer/components/text.ts | 54 ++++++++++++------- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx b/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx index 08fef2f9a6b7..d4703e9dfaa3 100644 --- a/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx +++ b/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx @@ -10,6 +10,7 @@ import { useSnapInterfaceContext } from '../../../../contexts/snaps'; export type SnapUIButtonProps = { name?: string; + textVariant: ButtonLinkProps<'button'>['variant']; }; const COLORS = { @@ -27,6 +28,7 @@ export const SnapUIButton: FunctionComponent< variant = 'primary', disabled = false, className = '', + textVariant, ...props }) => { const { handleEvent } = useSnapInterfaceContext(); @@ -58,6 +60,7 @@ export const SnapUIButton: FunctionComponent< onClick={handleClick} color={color} disabled={disabled} + variant={textVariant} {...props} > {children} diff --git a/ui/components/app/snaps/snap-ui-renderer/components/button.ts b/ui/components/app/snaps/snap-ui-renderer/components/button.ts index f624ffb23195..c136b92b5dd3 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/button.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/button.ts @@ -2,6 +2,7 @@ import { ButtonElement, JSXElement } from '@metamask/snaps-sdk/jsx'; import { getJsxChildren } from '@metamask/snaps-utils'; import { NonEmptyArray } from '@metamask/utils'; import { mapTextToTemplate } from '../utils'; +import { TextVariant } from '../../../../../helpers/constants/design-system'; import { UIComponentFactory } from './types'; export const button: UIComponentFactory = ({ @@ -15,6 +16,8 @@ export const button: UIComponentFactory = ({ variant: element.props.variant, name: element.props.name, disabled: element.props.disabled, + textVariant: + element.props.size === 'sm' ? TextVariant.bodySm : TextVariant.bodyMd, }, children: mapTextToTemplate( getJsxChildren(element) as NonEmptyArray, diff --git a/ui/components/app/snaps/snap-ui-renderer/components/text.ts b/ui/components/app/snaps/snap-ui-renderer/components/text.ts index fe9194817ca3..96bb6c520e48 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/text.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/text.ts @@ -6,32 +6,45 @@ import { TextVariant, OverflowWrap, TextColor, + FontWeight, } from '../../../../../helpers/constants/design-system'; import { UIComponentFactory } from './types'; +function getTextColor(color: TextElement['props']['color']) { + switch (color) { + case 'default': + return TextColor.textDefault; + case 'alternative': + return TextColor.textAlternative; + case 'muted': + return TextColor.textMuted; + case 'error': + return TextColor.errorDefault; + case 'success': + return TextColor.successDefault; + case 'warning': + return TextColor.warningDefault; + default: + return TextColor.inherit; + } +} + +function getFontWeight(color: TextElement['props']['fontWeight']) { + switch (color) { + case 'bold': + return FontWeight.Bold; + case 'medium': + return FontWeight.Medium; + case 'regular': + default: + return FontWeight.Normal; + } +} + export const text: UIComponentFactory = ({ element, ...params }) => { - const getTextColor = () => { - switch (element.props.color) { - case 'default': - return TextColor.textDefault; - case 'alternative': - return TextColor.textAlternative; - case 'muted': - return TextColor.textMuted; - case 'error': - return TextColor.errorDefault; - case 'success': - return TextColor.successDefault; - case 'warning': - return TextColor.warningDefault; - default: - return TextColor.inherit; - } - }; - return { element: 'Text', children: mapTextToTemplate( @@ -41,8 +54,9 @@ export const text: UIComponentFactory = ({ props: { variant: element.props.size === 'sm' ? TextVariant.bodySm : TextVariant.bodyMd, + fontWeight: getFontWeight(element.props.fontWeight), overflowWrap: OverflowWrap.Anywhere, - color: getTextColor(), + color: getTextColor(element.props.color), className: 'snap-ui-renderer__text', textAlign: element.props.alignment, }, From a8cd862b3de1f029a912954274a8681cd6dc48bd Mon Sep 17 00:00:00 2001 From: David Drazic Date: Tue, 17 Dec 2024 15:04:42 +0100 Subject: [PATCH 3/6] feat(snaps): Add Snap UI Banner component (#29271) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Snap UI Banner component. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29271?quickstart=1) Fixes: https://github.com/MetaMask/snaps/issues/2939 1. Install test Snap from the code example provided and check the results. Example Snap code with Banners in the UI: ```typescript  Here is the banner content! Banner bold formatted text Banner italic formatted text You can click here:  Banner link Here is the banner content! Here is the banner content! Here is the banner content! ``` Banner in Snap UI was not available before this PR. Nothing to show here. ![Screenshot 2024-12-17 at 12 34 42](https://github.com/user-attachments/assets/bdf291be-b41b-4e0a-964c-218dbd2dae5e) - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- .../safe-component-list.js | 2 ++ .../app/snaps/snap-ui-banner/index.ts | 1 + .../snap-ui-banner/snap-ui-banner.stories.tsx | 33 +++++++++++++++++++ .../snaps/snap-ui-banner/snap-ui-banner.tsx | 19 +++++++++++ .../snap-ui-renderer/components/banner.ts | 20 +++++++++++ .../snap-ui-renderer/components/index.ts | 2 ++ 6 files changed, 77 insertions(+) create mode 100644 ui/components/app/snaps/snap-ui-banner/index.ts create mode 100644 ui/components/app/snaps/snap-ui-banner/snap-ui-banner.stories.tsx create mode 100644 ui/components/app/snaps/snap-ui-banner/snap-ui-banner.tsx create mode 100644 ui/components/app/snaps/snap-ui-renderer/components/banner.ts diff --git a/ui/components/app/metamask-template-renderer/safe-component-list.js b/ui/components/app/metamask-template-renderer/safe-component-list.js index 962b630562d1..b15bff11247c 100644 --- a/ui/components/app/metamask-template-renderer/safe-component-list.js +++ b/ui/components/app/metamask-template-renderer/safe-component-list.js @@ -33,6 +33,7 @@ import { Copyable } from '../snaps/copyable'; import { SnapDelineator } from '../snaps/snap-delineator'; import { SnapUIAddress } from '../snaps/snap-ui-address'; import { SnapUIAvatar } from '../snaps/snap-ui-avatar'; +import { SnapUIBanner } from '../snaps/snap-ui-banner'; import { SnapUIButton } from '../snaps/snap-ui-button'; import { SnapUICard } from '../snaps/snap-ui-card'; import { SnapUICheckbox } from '../snaps/snap-ui-checkbox'; @@ -89,6 +90,7 @@ export const safeComponentList = { SnapDelineator, SnapUIAddress, SnapUIAvatar, + SnapUIBanner, SnapUIButton, SnapUICard, SnapUICheckbox, diff --git a/ui/components/app/snaps/snap-ui-banner/index.ts b/ui/components/app/snaps/snap-ui-banner/index.ts new file mode 100644 index 000000000000..f7e12ce782b0 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-banner/index.ts @@ -0,0 +1 @@ +export * from './snap-ui-banner'; diff --git a/ui/components/app/snaps/snap-ui-banner/snap-ui-banner.stories.tsx b/ui/components/app/snaps/snap-ui-banner/snap-ui-banner.stories.tsx new file mode 100644 index 000000000000..4d1581e9d3e8 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-banner/snap-ui-banner.stories.tsx @@ -0,0 +1,33 @@ +import React, { ReactNode } from 'react'; +import { SnapUIBanner } from './snap-ui-banner'; +import { BannerAlertSeverity } from '../../../component-library'; + +export default { + title: 'Components/App/Snaps/SnapUIBanner', + component: SnapUIBanner, + argTypes: { + title: { + control: 'text', + }, + severity: { + control: 'text', + }, + children: { + control: 'text', + }, + }, +}; + +export const DefaultStory = (args: { + title: string; + severity: BannerAlertSeverity; + children: ReactNode; +}) => ; + +DefaultStory.storyName = 'Default'; + +DefaultStory.args = { + title: 'Banner title', + severity: 'info', + children: 'Banner content.', +}; diff --git a/ui/components/app/snaps/snap-ui-banner/snap-ui-banner.tsx b/ui/components/app/snaps/snap-ui-banner/snap-ui-banner.tsx new file mode 100644 index 000000000000..872eb3032993 --- /dev/null +++ b/ui/components/app/snaps/snap-ui-banner/snap-ui-banner.tsx @@ -0,0 +1,19 @@ +import React, { FunctionComponent } from 'react'; +import { BannerAlert, BannerAlertSeverity } from '../../../component-library'; + +export type SnapUIBannerProps = { + severity: BannerAlertSeverity | undefined; + title: string; +}; + +export const SnapUIBanner: FunctionComponent = ({ + children, + severity, + title, +}) => { + return ( + + {children} + + ); +}; diff --git a/ui/components/app/snaps/snap-ui-renderer/components/banner.ts b/ui/components/app/snaps/snap-ui-renderer/components/banner.ts new file mode 100644 index 000000000000..fceb03d4be5d --- /dev/null +++ b/ui/components/app/snaps/snap-ui-renderer/components/banner.ts @@ -0,0 +1,20 @@ +import { BannerElement, JSXElement } from '@metamask/snaps-sdk/jsx'; +import { getJsxChildren } from '@metamask/snaps-utils'; +import { mapToTemplate } from '../utils'; +import { UIComponentFactory } from './types'; + +export const banner: UIComponentFactory = ({ + element, + ...params +}) => { + return { + element: 'SnapUIBanner', + children: getJsxChildren(element).map((children) => + mapToTemplate({ element: children as JSXElement, ...params }), + ), + props: { + title: element.props.title, + severity: element.props.severity, + }, + }; +}; diff --git a/ui/components/app/snaps/snap-ui-renderer/components/index.ts b/ui/components/app/snaps/snap-ui-renderer/components/index.ts index 17a9b6aa37c1..f6173b7199b0 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/index.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/index.ts @@ -27,6 +27,7 @@ import { selector } from './selector'; import { icon } from './icon'; import { section } from './section'; import { avatar } from './avatar'; +import { banner } from './banner'; export const COMPONENT_MAPPING = { Box: box, @@ -58,4 +59,5 @@ export const COMPONENT_MAPPING = { Container: container, Selector: selector, Section: section, + Banner: banner, }; From a97f2b4991288c941523e6c759da8fb5be5cbc47 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 17 Dec 2024 15:25:30 +0100 Subject: [PATCH 4/6] Update snapshots --- .../snaps-section/__snapshots__/snaps-section.test.tsx.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/pages/confirmations/components/confirm/snaps/snaps-section/__snapshots__/snaps-section.test.tsx.snap b/ui/pages/confirmations/components/confirm/snaps/snaps-section/__snapshots__/snaps-section.test.tsx.snap index 12e984bc207a..656b1fc49740 100644 --- a/ui/pages/confirmations/components/confirm/snaps/snaps-section/__snapshots__/snaps-section.test.tsx.snap +++ b/ui/pages/confirmations/components/confirm/snaps/snaps-section/__snapshots__/snaps-section.test.tsx.snap @@ -46,7 +46,7 @@ exports[`SnapsSection renders section for typed sign request 1`] = ` style="overflow-y: auto;" >

Hello world again!

@@ -104,7 +104,7 @@ exports[`SnapsSection renders section personal sign request 1`] = ` style="overflow-y: auto;" >

Hello world!

From bf1e4d6c890bb258add7a10cba52ba31003258ae Mon Sep 17 00:00:00 2001 From: David Drazic Date: Tue, 17 Dec 2024 16:14:47 +0100 Subject: [PATCH 5/6] feat(snaps): Add loading variant to Snap UI button (#28997) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR enables loading button variant for Snaps UI. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/28997?quickstart=1) #### Notes - Targets Snaps release integration PR: https://github.com/MetaMask/metamask-extension/pull/29275 ## **Related issues** Fixes: https://github.com/MetaMask/snaps/issues/2694 ## **Related PR dependencies** Snaps monorepo: https://github.com/MetaMask/snaps/pull/2930 ## **Manual testing steps** 1. Create some example Snap for testing and add Snap UI Button with `loading` variant. Example: ```TypeScript ``` ## **Screenshots/Recordings** ### **Before** Loading button variant was not available before. ### **After** https://github.com/user-attachments/assets/5afea22c-1951-4475-a908-aa5b97eafb6b ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- .../snaps/snap-ui-button/snap-ui-button.tsx | 18 ++++++++++++++++-- .../snap-ui-footer-button.tsx | 14 ++++++++++++-- .../snap-ui-renderer/components/button.ts | 1 + 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx b/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx index d4703e9dfaa3..49f1de8fbee1 100644 --- a/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx +++ b/ui/components/app/snaps/snap-ui-button/snap-ui-button.tsx @@ -1,7 +1,12 @@ import React, { FunctionComponent, MouseEvent as ReactMouseEvent } from 'react'; import classnames from 'classnames'; import { ButtonType, UserInputEventType } from '@metamask/snaps-sdk'; -import { ButtonLinkProps, Text } from '../../../component-library'; +import { + ButtonLinkProps, + Icon, + IconName, + Text, +} from '../../../component-library'; import { FontWeight, TextColor, @@ -11,6 +16,7 @@ import { useSnapInterfaceContext } from '../../../../contexts/snaps'; export type SnapUIButtonProps = { name?: string; textVariant: ButtonLinkProps<'button'>['variant']; + loading?: boolean; }; const COLORS = { @@ -27,6 +33,7 @@ export const SnapUIButton: FunctionComponent< type = ButtonType.Button, variant = 'primary', disabled = false, + loading = false, className = '', textVariant, ...props @@ -63,7 +70,14 @@ export const SnapUIButton: FunctionComponent< variant={textVariant} {...props} > - {children} + {loading ? ( + + ) : ( + children + )} ); }; diff --git a/ui/components/app/snaps/snap-ui-footer-button/snap-ui-footer-button.tsx b/ui/components/app/snaps/snap-ui-footer-button/snap-ui-footer-button.tsx index 240024bb673a..ff3f530ae1e7 100644 --- a/ui/components/app/snaps/snap-ui-footer-button/snap-ui-footer-button.tsx +++ b/ui/components/app/snaps/snap-ui-footer-button/snap-ui-footer-button.tsx @@ -10,6 +10,8 @@ import { Button, ButtonProps, ButtonSize, + Icon, + IconName, IconSize, } from '../../../component-library'; import { @@ -35,6 +37,7 @@ export const SnapUIFooterButton: FunctionComponent< name, children, disabled = false, + loading = false, isSnapAction = false, type, variant = ButtonVariant.Primary, @@ -85,10 +88,17 @@ export const SnapUIFooterButton: FunctionComponent< flexDirection: FlexDirection.Row, }} > - {isSnapAction && !hideSnapBranding && ( + {isSnapAction && !hideSnapBranding && !loading && ( )} - {children} + {loading ? ( + + ) : ( + children + )} ); }; diff --git a/ui/components/app/snaps/snap-ui-renderer/components/button.ts b/ui/components/app/snaps/snap-ui-renderer/components/button.ts index c136b92b5dd3..4b0cdb808e79 100644 --- a/ui/components/app/snaps/snap-ui-renderer/components/button.ts +++ b/ui/components/app/snaps/snap-ui-renderer/components/button.ts @@ -16,6 +16,7 @@ export const button: UIComponentFactory = ({ variant: element.props.variant, name: element.props.name, disabled: element.props.disabled, + loading: element.props.loading, textVariant: element.props.size === 'sm' ? TextVariant.bodySm : TextVariant.bodyMd, }, From 242a6d2d627d6f568fb86b47c6f08dcfb6297dc3 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 17 Dec 2024 18:24:09 +0100 Subject: [PATCH 6/6] feat(snaps): Settings Page (#29234) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR adds a settings section for each preinstalled snaps that exposes a `onSettingsPage` handler [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29234?quickstart=1) ## **Related issues** Fixes: https://github.com/MetaMask/snaps/issues/2874 ## **Manual testing steps** 1. Open MetaMask's settings 2. You should see the preinstalled snaps settings ## **Screenshots/Recordings** ### **Before** ### **After** ![image](https://github.com/user-attachments/assets/55f4027c-258e-4039-9006-95228cfdca3f) ![image](https://github.com/user-attachments/assets/e05e6964-ac61-49a1-bb28-e8d7d077dadc) ![image](https://github.com/user-attachments/assets/b2db7088-069a-4b83-9740-11f23f36e879) ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- shared/constants/snaps/permissions.ts | 1 + .../app/snaps/snap-settings-page/index.ts | 1 + .../snap-settings-renderer.tsx | 81 +++++++++++++++++++ ui/components/app/tab-bar/index.scss | 5 ++ ui/helpers/constants/routes.ts | 3 + ui/helpers/utils/snaps.js | 14 ---- ui/helpers/utils/snaps.ts | 40 +++++++++ ui/hooks/snaps/useSnapSettings.ts | 55 +++++++++++++ .../info/personal-sign/personal-sign.test.tsx | 4 +- .../info/typed-sign-v1/typed-sign-v1.test.tsx | 4 +- .../info/typed-sign/typed-sign.test.tsx | 4 +- ui/pages/settings/index.scss | 3 +- ui/pages/settings/settings.component.js | 37 ++++++++- ui/pages/settings/settings.container.js | 21 +++++ ui/pages/settings/settings.stories.js | 1 + ui/selectors/selectors.js | 20 +++++ 16 files changed, 270 insertions(+), 24 deletions(-) create mode 100644 ui/components/app/snaps/snap-settings-page/index.ts create mode 100644 ui/components/app/snaps/snap-settings-page/snap-settings-renderer.tsx delete mode 100644 ui/helpers/utils/snaps.js create mode 100644 ui/helpers/utils/snaps.ts create mode 100644 ui/hooks/snaps/useSnapSettings.ts diff --git a/shared/constants/snaps/permissions.ts b/shared/constants/snaps/permissions.ts index 837beea4d9ff..e08afdf46421 100644 --- a/shared/constants/snaps/permissions.ts +++ b/shared/constants/snaps/permissions.ts @@ -7,6 +7,7 @@ export const EndowmentPermissions = Object.freeze({ 'endowment:webassembly': 'endowment:webassembly', 'endowment:lifecycle-hooks': 'endowment:lifecycle-hooks', 'endowment:page-home': 'endowment:page-home', + 'endowment:page-settings': 'endowment:page-settings', 'endowment:signature-insight': 'endowment:signature-insight', 'endowment:name-lookup': 'endowment:name-lookup', ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) diff --git a/ui/components/app/snaps/snap-settings-page/index.ts b/ui/components/app/snaps/snap-settings-page/index.ts new file mode 100644 index 000000000000..91a594fcdeb0 --- /dev/null +++ b/ui/components/app/snaps/snap-settings-page/index.ts @@ -0,0 +1 @@ +export * from './snap-settings-renderer'; diff --git a/ui/components/app/snaps/snap-settings-page/snap-settings-renderer.tsx b/ui/components/app/snaps/snap-settings-page/snap-settings-renderer.tsx new file mode 100644 index 000000000000..fa5636ebb77f --- /dev/null +++ b/ui/components/app/snaps/snap-settings-page/snap-settings-renderer.tsx @@ -0,0 +1,81 @@ +import React, { FunctionComponent, useEffect, useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useLocation } from 'react-router-dom'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; + +import { deleteInterface } from '../../../../store/actions'; +import { Box, Text } from '../../../component-library'; +import { + BackgroundColor, + BlockSize, + TextVariant, +} from '../../../../helpers/constants/design-system'; +import { SnapDelineator } from '../snap-delineator'; +import { getSnapMetadata } from '../../../../selectors'; +import { DelineatorType } from '../../../../helpers/constants/snaps'; +import { Copyable } from '../copyable'; +import { SnapUIRenderer } from '../snap-ui-renderer'; +import { useSnapSettings } from '../../../../hooks/snaps/useSnapSettings'; +import { decodeSnapIdFromPathname } from '../../../../helpers/utils/snaps'; + +type SnapSettingsRendererProps = { + snapId: string; +}; + +export const SnapSettingsRenderer: FunctionComponent< + SnapSettingsRendererProps +> = () => { + const { pathname } = useLocation(); + const dispatch = useDispatch(); + const t = useI18nContext(); + + const snapId = useMemo(() => decodeSnapIdFromPathname(pathname), [pathname]); + + const { name: snapName } = useSelector((state) => + getSnapMetadata(state, snapId), + ); + + const { data, error, loading } = useSnapSettings({ + snapId, + }); + + const interfaceId = !loading && !error ? data?.id : undefined; + + useEffect(() => { + return () => { + interfaceId && dispatch(deleteInterface(interfaceId)); + }; + }, [interfaceId]); + + if (!snapId) { + return null; + } + + return ( + + {error && ( + + + + {t('snapsUIError', [{snapName}])} + + + + + )} + {(interfaceId || loading) && ( + + )} + + ); +}; diff --git a/ui/components/app/tab-bar/index.scss b/ui/components/app/tab-bar/index.scss index ce149f922879..6efdbab6c51e 100644 --- a/ui/components/app/tab-bar/index.scss +++ b/ui/components/app/tab-bar/index.scss @@ -38,11 +38,16 @@ display: flex; align-items: center; position: relative; + overflow: hidden; width: 100%; &__title { @include design-system.H4; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + @include design-system.screen-sm-min { @include design-system.H6; } diff --git a/ui/helpers/constants/routes.ts b/ui/helpers/constants/routes.ts index da12a52be812..ac21c32a2b1b 100644 --- a/ui/helpers/constants/routes.ts +++ b/ui/helpers/constants/routes.ts @@ -62,6 +62,9 @@ PATH_NAME_MAP[CONTACT_ADD_ROUTE] = 'Add Contact Settings Page'; export const CONTACT_VIEW_ROUTE = '/settings/contact-list/view-contact'; PATH_NAME_MAP[`${CONTACT_VIEW_ROUTE}/:address`] = 'View Contact Settings Page'; +export const SNAP_SETTINGS_ROUTE = '/settings/snap'; +PATH_NAME_MAP[`${SNAP_SETTINGS_ROUTE}/:snapId`] = 'Snap Settings Page'; + export const REVEAL_SEED_ROUTE = '/seed'; PATH_NAME_MAP[REVEAL_SEED_ROUTE] = 'Reveal Secret Recovery Phrase Page'; diff --git a/ui/helpers/utils/snaps.js b/ui/helpers/utils/snaps.js deleted file mode 100644 index 8a57ce516cf2..000000000000 --- a/ui/helpers/utils/snaps.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Check if the given value is a valid snap ID. - * - * NOTE: This function is a duplicate oF a yet to be released version in @metamask/snaps-utils. - * - * @param value - The value to check. - * @returns `true` if the value is a valid snap ID, and `false` otherwise. - */ -export function isSnapId(value) { - return ( - (typeof value === 'string' || value instanceof String) && - (value.startsWith('local:') || value.startsWith('npm:')) - ); -} diff --git a/ui/helpers/utils/snaps.ts b/ui/helpers/utils/snaps.ts new file mode 100644 index 000000000000..c788393e83ab --- /dev/null +++ b/ui/helpers/utils/snaps.ts @@ -0,0 +1,40 @@ +import { SnapId } from '@metamask/snaps-sdk'; +import { isProduction } from '../../../shared/modules/environment'; + +/** + * Check if the given value is a valid snap ID. + * + * NOTE: This function is a duplicate oF a yet to be released version in @metamask/snaps-utils. + * + * @param value - The value to check. + * @returns `true` if the value is a valid snap ID, and `false` otherwise. + */ +export function isSnapId(value: unknown): value is SnapId { + return ( + (typeof value === 'string' || value instanceof String) && + (value.startsWith('local:') || value.startsWith('npm:')) + ); +} + +/** + * Decode a snap ID fron a pathname. + * + * @param pathname - The pathname to decode the snap ID from. + * @returns The decoded snap ID, or `undefined` if the snap ID could not be decoded. + */ +export const decodeSnapIdFromPathname = (pathname: string) => { + const snapIdURI = pathname?.match(/[^/]+$/u)?.[0]; + return snapIdURI && decodeURIComponent(snapIdURI); +}; + +const IGNORED_EXAMPLE_SNAPS = ['npm:@metamask/preinstalled-example-snap']; + +/** + * Check if the given snap ID is ignored in production. + * + * @param snapId - The snap ID to check. + * @returns `true` if the snap ID is ignored in production, and `false` otherwise. + */ +export const isSnapIgnoredInProd = (snapId: string) => { + return isProduction() ? IGNORED_EXAMPLE_SNAPS.includes(snapId) : false; +}; diff --git a/ui/hooks/snaps/useSnapSettings.ts b/ui/hooks/snaps/useSnapSettings.ts new file mode 100644 index 000000000000..ad2debc8cac2 --- /dev/null +++ b/ui/hooks/snaps/useSnapSettings.ts @@ -0,0 +1,55 @@ +import { useEffect, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { + forceUpdateMetamaskState, + handleSnapRequest, +} from '../../store/actions'; + +export function useSnapSettings({ snapId }: { snapId?: string }) { + const dispatch = useDispatch(); + const [loading, setLoading] = useState(true); + const [data, setData] = useState<{ id: string } | undefined>(undefined); + const [error, setError] = useState(undefined); + + useEffect(() => { + let cancelled = false; + async function fetchPage(id: string) { + try { + setError(undefined); + setLoading(true); + + const newData = (await handleSnapRequest({ + snapId: id, + origin: '', + handler: 'onSettingsPage', + request: { + jsonrpc: '2.0', + method: ' ', + }, + })) as { id: string }; + if (!cancelled) { + setData(newData); + forceUpdateMetamaskState(dispatch); + } + } catch (err) { + if (!cancelled) { + setError(err as Error); + } + } finally { + if (!cancelled) { + setLoading(false); + } + } + } + + if (snapId) { + fetchPage(snapId); + } + + return () => { + cancelled = true; + }; + }, [snapId]); + + return { data, error, loading }; +} diff --git a/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.test.tsx b/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.test.tsx index 3b5d1dfc3e62..336ef3f9f92f 100644 --- a/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/personal-sign/personal-sign.test.tsx @@ -149,7 +149,7 @@ describe('PersonalSignInfo', () => { getMockPersonalSignConfirmStateForRequest(signatureRequestSIWE); (utils.isSIWESignatureRequest as jest.Mock).mockReturnValue(false); - (snapUtils.isSnapId as jest.Mock).mockReturnValue(true); + (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(true); const mockStore = configureMockStore([])(state); const { queryByText, getByText } = renderWithConfirmContextProvider( @@ -171,7 +171,7 @@ describe('PersonalSignInfo', () => { const state = getMockPersonalSignConfirmStateForRequest(signatureRequestSIWE); (utils.isSIWESignatureRequest as jest.Mock).mockReturnValue(false); - (snapUtils.isSnapId as jest.Mock).mockReturnValue(true); + (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(true); const mockStore = configureMockStore([])(state); const { getByText, queryByText } = renderWithConfirmContextProvider( diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign-v1/typed-sign-v1.test.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign-v1/typed-sign-v1.test.tsx index 2b1e6969ddd5..83696b69ac64 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign-v1/typed-sign-v1.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign-v1/typed-sign-v1.test.tsx @@ -65,7 +65,7 @@ describe('TypedSignInfo', () => { type: TransactionType.signTypedData, chainId: '0x5', }); - (snapUtils.isSnapId as jest.Mock).mockReturnValue(true); + (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(true); const mockStore = configureMockStore([])(mockState); const { queryByText } = renderWithConfirmContextProvider( , @@ -88,7 +88,7 @@ describe('TypedSignInfo', () => { type: TransactionType.signTypedData, chainId: '0x5', }); - (snapUtils.isSnapId as jest.Mock).mockReturnValue(false); + (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(false); const mockStore = configureMockStore([])(mockState); const { queryByText } = renderWithConfirmContextProvider( , diff --git a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx index 56421561ccd2..640b663e5cc1 100644 --- a/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/typed-sign/typed-sign.test.tsx @@ -153,7 +153,7 @@ describe('TypedSignInfo', () => { type: TransactionType.signTypedData, chainId: '0x5', }); - (snapUtils.isSnapId as jest.Mock).mockReturnValue(true); + (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(true); const mockStore = configureMockStore([])(mockState); const { queryByText } = renderWithConfirmContextProvider( , @@ -177,7 +177,7 @@ describe('TypedSignInfo', () => { type: TransactionType.signTypedData, chainId: '0x5', }); - (snapUtils.isSnapId as jest.Mock).mockReturnValue(false); + (snapUtils.isSnapId as unknown as jest.Mock).mockReturnValue(false); const mockStore = configureMockStore([])(mockState); const { queryByText } = renderWithConfirmContextProvider( , diff --git a/ui/pages/settings/index.scss b/ui/pages/settings/index.scss index f57e1c310998..f9b08529ee65 100644 --- a/ui/pages/settings/index.scss +++ b/ui/pages/settings/index.scss @@ -56,7 +56,7 @@ &__title { @include design-system.screen-sm-min { - width: 197px; + margin-right: 16px; } @include design-system.screen-sm-max { @@ -230,6 +230,7 @@ display: flex; flex-direction: column; flex: 1 1 auto; + max-width: 100vw; @include design-system.screen-sm-min { flex: 0 0 40%; diff --git a/ui/pages/settings/settings.component.js b/ui/pages/settings/settings.component.js index 724c661c9aeb..37257e2c8fcb 100644 --- a/ui/pages/settings/settings.component.js +++ b/ui/pages/settings/settings.component.js @@ -21,6 +21,7 @@ import { ADD_POPULAR_CUSTOM_NETWORK, DEFAULT_ROUTE, NOTIFICATIONS_SETTINGS_ROUTE, + SNAP_SETTINGS_ROUTE, } from '../../helpers/constants/routes'; import { getSettingsRoutes } from '../../helpers/utils/settings-search'; @@ -31,6 +32,7 @@ import { IconName, Box, Text, + IconSize, } from '../../components/component-library'; import { AlignItems, @@ -44,6 +46,8 @@ import MetafoxLogo from '../../components/ui/metafox-logo'; // eslint-disable-next-line import/no-restricted-paths import { getEnvironmentType } from '../../../app/scripts/lib/util'; import { ENVIRONMENT_TYPE_POPUP } from '../../../shared/constants/app'; +import { SnapIcon } from '../../components/app/snaps/snap-icon'; +import { SnapSettingsRenderer } from '../../components/app/snaps/snap-settings-page'; import SettingsTab from './settings-tab'; import AdvancedTab from './advanced-tab'; import InfoTab from './info-tab'; @@ -70,6 +74,8 @@ class SettingsPage extends PureComponent { mostRecentOverviewPage: PropTypes.string.isRequired, pathnameI18nKey: PropTypes.string, remoteFeatureFlags: PropTypes.object.isRequired, + settingsPageSnaps: PropTypes.array, + snapSettingsTitle: PropTypes.string, toggleNetworkMenu: PropTypes.func.isRequired, useExternalServices: PropTypes.bool, }; @@ -210,19 +216,24 @@ class SettingsPage extends PureComponent { renderTitle() { const { t } = this.context; - const { isPopup, pathnameI18nKey, addressName } = this.props; + const { isPopup, pathnameI18nKey, addressName, snapSettingsTitle } = + this.props; let titleText; if (isPopup && addressName) { titleText = t('details'); } else if (pathnameI18nKey && isPopup) { titleText = t(pathnameI18nKey); + } else if (snapSettingsTitle) { + titleText = snapSettingsTitle; } else { titleText = t('settings'); } return (
- {titleText} + + {titleText} +
); } @@ -293,15 +304,31 @@ class SettingsPage extends PureComponent { } renderTabs() { - const { history, currentPath, useExternalServices } = this.props; + const { history, currentPath, useExternalServices, settingsPageSnaps } = + this.props; const { t } = this.context; + const snapsSettings = settingsPageSnaps.map(({ id, name }) => { + return { + content: name, + icon: ( + + ), + key: `${SNAP_SETTINGS_ROUTE}/${encodeURIComponent(id)}`, + }; + }); + const tabs = [ { content: t('general'), icon: , key: GENERAL_ROUTE, }, + ...snapsSettings, { content: t('advanced'), icon: , @@ -390,6 +417,10 @@ class SettingsPage extends PureComponent { )} /> + { metamask: { currencyRates }, } = state; const remoteFeatureFlags = getRemoteFeatureFlags(state); + + const settingsPageSnapsIds = getSettingsPageSnapsIds(state); + const snapsMetadata = getSnapsMetadata(state); const conversionDate = currencyRates[ticker]?.conversionDate; const pathNameTail = pathname.match(/[^/]+$/u)[0]; @@ -75,6 +83,7 @@ const mapStateToProps = (state, ownProps) => { const isAddPopularCustomNetwork = Boolean( pathname.match(ADD_POPULAR_CUSTOM_NETWORK), ); + const isSnapSettingsRoute = Boolean(pathname.match(SNAP_SETTINGS_ROUTE)); const isPopup = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP; const pathnameI18nKey = ROUTES_TO_I18N_KEYS[pathname]; @@ -102,6 +111,16 @@ const mapStateToProps = (state, ownProps) => { ); const useExternalServices = getUseExternalServices(state); + const snapNameGetter = getSnapName(snapsMetadata); + + const settingsPageSnaps = settingsPageSnapsIds.map((snapId) => ({ + id: snapId, + name: snapNameGetter(snapId), + })); + + const snapSettingsTitle = + isSnapSettingsRoute && snapNameGetter(decodeSnapIdFromPathname(pathname)); + return { addNewNetwork, addressName, @@ -115,6 +134,8 @@ const mapStateToProps = (state, ownProps) => { mostRecentOverviewPage: getMostRecentOverviewPage(state), pathnameI18nKey, remoteFeatureFlags, + settingsPageSnaps, + snapSettingsTitle, useExternalServices, }; }; diff --git a/ui/pages/settings/settings.stories.js b/ui/pages/settings/settings.stories.js index 53437f4175db..b6b695a89cb1 100644 --- a/ui/pages/settings/settings.stories.js +++ b/ui/pages/settings/settings.stories.js @@ -62,6 +62,7 @@ const Settings = ({ history }) => { pathnameI18nKey={pathnameI18nKey} backRoute={SETTINGS_ROUTE} remoteFeatureFlags={{}} + settingsPageSnaps={[]} /> ); diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 403db6cadb91..ce117af76262 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -13,6 +13,7 @@ import { NameType } from '@metamask/name-controller'; import { TransactionStatus } from '@metamask/transaction-controller'; import { isEvmAccountType } from '@metamask/keyring-api'; import { RpcEndpointType } from '@metamask/network-controller'; +import { SnapEndowments } from '@metamask/snaps-rpc-methods'; import { getCurrentChainId, getProviderConfig, @@ -111,6 +112,7 @@ import { BridgeFeatureFlagsKey } from '../../shared/types/bridge'; import { hasTransactionData } from '../../shared/modules/transaction.utils'; import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils'; import { createDeepEqualSelector } from '../../shared/modules/selectors/util'; +import { isSnapIgnoredInProd } from '../helpers/utils/snaps'; import { getAllUnapprovedTransactions, getCurrentNetworkTransactions, @@ -1918,6 +1920,19 @@ export const getInsightSnaps = createDeepEqualSelector( }, ); +export const getSettingsPageSnaps = createDeepEqualSelector( + getEnabledSnaps, + getPermissionSubjects, + (snaps, subjects) => { + return Object.values(snaps).filter( + ({ id, preinstalled }) => + subjects[id]?.permissions[SnapEndowments.SettingsPage] && + preinstalled && + !isSnapIgnoredInProd(id), + ); + }, +); + export const getSignatureInsightSnaps = createDeepEqualSelector( getEnabledSnaps, getPermissionSubjects, @@ -1948,6 +1963,11 @@ export const getNameLookupSnapsIds = createDeepEqualSelector( }, ); +export const getSettingsPageSnapsIds = createDeepEqualSelector( + getSettingsPageSnaps, + (snaps) => snaps.map((snap) => snap.id), +); + export const getNotifySnaps = createDeepEqualSelector( getEnabledSnaps, getPermissionSubjects,