Skip to content

Commit d81d69b

Browse files
feat: Integrate Snaps into the redesigned confirmations (MetaMask#26435)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> This PR integrates Snaps insights into the redesigned signature confirmations, showing them at the bottom using the new `Delineator` component. Snaps are exposed to the new confirmations via `SnapsSection` and `SnapInsight`, these two components use the newly written `useInsightSnaps` hook. By request of @eriknson this PR makes some slight adjustments to the `Delineator` component, tweaking the font colors, adding a disabled state etc. This PR does not integrate Snaps into the alert system, that'll be done in a follow-up PR at a later date. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/26435?quickstart=1) ## **Related issues** Closes MetaMask/snaps#2530 ## **Manual testing steps** 1. Install the signature insights example Snap from https://metamask.github.io/snaps/test-snaps/latest/ 2. Use the test-dapp to trigger any signature confirmation 3. See that insights are now present at the bottom of the signature confirmation. ## **Screenshots/Recordings** ![image](https://github.com/user-attachments/assets/55476591-34b4-4da3-afb5-91a634494590)
1 parent d2d48d2 commit d81d69b

File tree

11 files changed

+358
-12
lines changed

11 files changed

+358
-12
lines changed

app/_locales/en/messages.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/components/ui/delineator/delineator.tsx

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const ExpandableIcon = ({ isExpanded }: { isExpanded: boolean }) => {
3838
<Icon
3939
name={isExpanded ? IconName.ArrowUp : IconName.ArrowDown}
4040
size={IconSize.Sm}
41-
color={IconColor.iconMuted}
41+
color={IconColor.primaryDefault}
4242
/>
4343
);
4444
};
@@ -49,14 +49,16 @@ const Header = ({
4949
isCollapsible,
5050
isExpanded,
5151
isLoading,
52+
isDisabled,
5253
onHeaderClick,
5354
type,
5455
}: {
5556
headerComponent: DelineatorProps['headerComponent'];
56-
iconName: IconName;
57+
iconName?: IconName;
5758
isCollapsible: boolean;
5859
isExpanded: boolean;
5960
isLoading: boolean;
61+
isDisabled: boolean;
6062
onHeaderClick: () => void;
6163
type?: DelineatorType;
6264
}) => {
@@ -67,18 +69,19 @@ const Header = ({
6769
delineator__header: true,
6870
'delineator__header--expanded': isExpanded,
6971
'delineator__header--loading': isLoading,
72+
'delineator__header--disabled': isDisabled,
7073
})}
7174
display={Display.Flex}
7275
alignItems={AlignItems.center}
7376
justifyContent={JustifyContent.spaceBetween}
7477
paddingTop={2}
7578
paddingRight={4}
76-
paddingBottom={2}
79+
paddingBottom={isExpanded ? 0 : 2}
7780
paddingLeft={4}
7881
onClick={onHeaderClick}
7982
>
8083
<Box display={Display.Flex} alignItems={AlignItems.center}>
81-
<AvatarIcon iconName={iconName} {...iconProps} />
84+
{iconName && <AvatarIcon iconName={iconName} {...iconProps} />}
8285
{overrideTextComponentColorByType({
8386
component: headerComponent,
8487
type,
@@ -89,14 +92,21 @@ const Header = ({
8992
</Box>
9093
);
9194
};
92-
const Content = ({ children }: { children: React.ReactNode }) => {
95+
const Content = ({
96+
children,
97+
contentBoxProps,
98+
}: {
99+
children: React.ReactNode;
100+
contentBoxProps: DelineatorProps['contentBoxProps'];
101+
}) => {
93102
return (
94103
<Box
95104
paddingTop={2}
96105
paddingRight={4}
97106
paddingBottom={4}
98107
paddingLeft={4}
99108
flexDirection={FlexDirection.Column}
109+
{...contentBoxProps}
100110
>
101111
{children}
102112
</Box>
@@ -131,21 +141,23 @@ export const Delineator: React.FC<DelineatorProps> = ({
131141
isCollapsible = true,
132142
isExpanded: isExpandedProp,
133143
isLoading = false,
144+
isDisabled = false,
134145
onExpandChange,
135146
type,
136147
wrapperBoxProps,
148+
contentBoxProps,
137149
}) => {
138150
const [isExpanded, setIsExpanded] = useState(isExpandedProp || false);
139151
const shouldShowContent = !isCollapsible || (isCollapsible && isExpanded);
140152

141153
const handleHeaderClick = useCallback(() => {
142-
if (isLoading || !isCollapsible) {
154+
if (isDisabled || isLoading || !isCollapsible) {
143155
return;
144156
}
145157
const newExpandedState = !isExpanded;
146158
onExpandChange?.(newExpandedState);
147159
setIsExpanded(newExpandedState);
148-
}, [isLoading, isCollapsible, isExpanded, onExpandChange]);
160+
}, [isLoading, isCollapsible, isExpanded, isDisabled, onExpandChange]);
149161

150162
return (
151163
<Container wrapperBoxProps={wrapperBoxProps}>
@@ -155,10 +167,13 @@ export const Delineator: React.FC<DelineatorProps> = ({
155167
isCollapsible={isCollapsible}
156168
isExpanded={isExpanded}
157169
isLoading={isLoading}
170+
isDisabled={isDisabled}
158171
onHeaderClick={handleHeaderClick}
159172
type={type}
160173
/>
161-
{shouldShowContent && !isLoading && <Content>{children}</Content>}
174+
{shouldShowContent && !isLoading && (
175+
<Content contentBoxProps={contentBoxProps}>{children}</Content>
176+
)}
162177
</Container>
163178
);
164179
};

ui/components/ui/delineator/delineator.types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import { Box, IconName, Text } from '../../component-library';
33
export type DelineatorProps = {
44
children?: React.ReactNode;
55
headerComponent: React.ReactElement<typeof Text>;
6-
iconName: IconName;
6+
iconName?: IconName;
77
isCollapsible?: boolean;
88
isExpanded?: boolean;
99
isLoading?: boolean;
10+
isDisabled?: boolean;
1011
onExpandChange?: (isExpanded: boolean) => void;
1112
type?: DelineatorType;
1213
wrapperBoxProps?: React.ComponentProps<typeof Box>;
14+
contentBoxProps?: React.ComponentProps<typeof Box>;
1315
};
1416

1517
export enum DelineatorType {

ui/components/ui/delineator/index.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,10 @@
55
&--loading {
66
cursor: default;
77
}
8+
9+
&--disabled {
10+
cursor: default;
11+
opacity: 0.5;
12+
}
813
}
914
}

ui/components/ui/delineator/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const getTextColorByType = (type?: DelineatorType) => {
4242
case DelineatorType.Error:
4343
return TextColor.errorDefault;
4444
default:
45-
return TextColor.textAlternative;
45+
return TextColor.textDefault;
4646
}
4747
};
4848

ui/pages/confirmations/components/confirm/pluggable-section/pluggable-section.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { ReactComponentLike } from 'prop-types';
33
import { useSelector } from 'react-redux';
44

55
import { currentConfirmationSelector } from '../../../selectors';
6+
import { SnapsSection } from '../snaps/snaps-section';
67

78
// Components to be plugged into confirmation page can be added to the array below
8-
const pluggedInSections: ReactComponentLike[] = [];
9+
const pluggedInSections: ReactComponentLike[] = [SnapsSection];
910

1011
const PluggableSection = () => {
1112
const currentConfirmation = useSelector(currentConfirmationSelector);
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`SnapsSection renders section for typed sign request 1`] = `
4+
<div>
5+
<div
6+
class="mm-box mm-box--margin-bottom-4 mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-column"
7+
>
8+
<div
9+
class="mm-box delineator__wrapper mm-box--display-flex mm-box--flex-direction-column mm-box--background-color-background-default mm-box--rounded-lg"
10+
>
11+
<div
12+
class="mm-box delineator__header delineator__header--expanded mm-box--padding-top-2 mm-box--padding-right-4 mm-box--padding-bottom-0 mm-box--padding-left-4 mm-box--display-flex mm-box--justify-content-space-between mm-box--align-items-center"
13+
>
14+
<div
15+
class="mm-box mm-box--display-flex mm-box--align-items-center"
16+
>
17+
<p
18+
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
19+
>
20+
<span>
21+
22+
Insights from
23+
<span
24+
class="mm-box mm-text mm-text--inherit mm-text--font-weight-medium mm-box--color-inherit"
25+
>
26+
BIP-32 Test Snap
27+
</span>
28+
29+
30+
</span>
31+
</p>
32+
</div>
33+
<span
34+
class="mm-box mm-icon mm-icon--size-sm mm-box--display-inline-block mm-box--color-primary-default"
35+
style="mask-image: url('./images/icons/arrow-up.svg');"
36+
/>
37+
</div>
38+
<div
39+
class="mm-box mm-box--padding-top-0 mm-box--padding-right-0 mm-box--padding-bottom-0 mm-box--padding-left-0 mm-box--flex-direction-column"
40+
>
41+
<div
42+
class="mm-box snap-ui-renderer__content mm-box--height-full"
43+
>
44+
<div
45+
class="box snap-ui-renderer__container box--display-flex box--flex-direction-column box--height-full"
46+
>
47+
<p
48+
class="mm-box mm-text snap-ui-renderer__text mm-text--body-md mm-text--overflow-wrap-anywhere mm-box--color-inherit"
49+
>
50+
Hello world again!
51+
</p>
52+
</div>
53+
</div>
54+
</div>
55+
</div>
56+
</div>
57+
</div>
58+
`;
59+
60+
exports[`SnapsSection renders section personal sign request 1`] = `
61+
<div>
62+
<div
63+
class="mm-box mm-box--margin-bottom-4 mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-column"
64+
>
65+
<div
66+
class="mm-box delineator__wrapper mm-box--display-flex mm-box--flex-direction-column mm-box--background-color-background-default mm-box--rounded-lg"
67+
>
68+
<div
69+
class="mm-box delineator__header delineator__header--expanded mm-box--padding-top-2 mm-box--padding-right-4 mm-box--padding-bottom-0 mm-box--padding-left-4 mm-box--display-flex mm-box--justify-content-space-between mm-box--align-items-center"
70+
>
71+
<div
72+
class="mm-box mm-box--display-flex mm-box--align-items-center"
73+
>
74+
<p
75+
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
76+
>
77+
<span>
78+
79+
Insights from
80+
<span
81+
class="mm-box mm-text mm-text--inherit mm-text--font-weight-medium mm-box--color-inherit"
82+
>
83+
BIP-32 Test Snap
84+
</span>
85+
86+
87+
</span>
88+
</p>
89+
</div>
90+
<span
91+
class="mm-box mm-icon mm-icon--size-sm mm-box--display-inline-block mm-box--color-primary-default"
92+
style="mask-image: url('./images/icons/arrow-up.svg');"
93+
/>
94+
</div>
95+
<div
96+
class="mm-box mm-box--padding-top-0 mm-box--padding-right-0 mm-box--padding-bottom-0 mm-box--padding-left-0 mm-box--flex-direction-column"
97+
>
98+
<div
99+
class="mm-box snap-ui-renderer__content mm-box--height-full"
100+
>
101+
<div
102+
class="box snap-ui-renderer__container box--display-flex box--flex-direction-column box--height-full"
103+
>
104+
<p
105+
class="mm-box mm-text snap-ui-renderer__text mm-text--body-md mm-text--overflow-wrap-anywhere mm-box--color-inherit"
106+
>
107+
Hello world!
108+
</p>
109+
</div>
110+
</div>
111+
</div>
112+
</div>
113+
</div>
114+
</div>
115+
`;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './snaps-section';
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React from 'react';
2+
import { useSelector } from 'react-redux';
3+
import { SnapUIRenderer } from '../../../../../../components/app/snaps/snap-ui-renderer';
4+
import { Delineator } from '../../../../../../components/ui/delineator';
5+
import { Text } from '../../../../../../components/component-library';
6+
import {
7+
TextColor,
8+
TextVariant,
9+
FontWeight,
10+
} from '../../../../../../helpers/constants/design-system';
11+
import { useI18nContext } from '../../../../../../hooks/useI18nContext';
12+
import { getSnapMetadata } from '../../../../../../selectors';
13+
import Tooltip from '../../../../../../components/ui/tooltip';
14+
15+
export type SnapInsightProps = {
16+
snapId: string;
17+
interfaceId: string;
18+
loading: boolean;
19+
};
20+
21+
export const SnapInsight: React.FunctionComponent<SnapInsightProps> = ({
22+
snapId,
23+
interfaceId,
24+
loading,
25+
}) => {
26+
const t = useI18nContext();
27+
const { name: snapName } = useSelector((state) =>
28+
/* @ts-expect-error wrong type on selector. */
29+
getSnapMetadata(state, snapId),
30+
);
31+
32+
const headerComponent = (
33+
<Text>
34+
{t('insightsFromSnap', [
35+
<Text
36+
fontWeight={FontWeight.Medium}
37+
variant={TextVariant.inherit}
38+
color={TextColor.inherit}
39+
>
40+
{snapName}
41+
</Text>,
42+
])}
43+
</Text>
44+
);
45+
46+
const hasNoInsight = !loading && !interfaceId;
47+
48+
if (hasNoInsight) {
49+
return (
50+
<Tooltip position="top" title={t('snapsNoInsight')}>
51+
<Delineator headerComponent={headerComponent} isDisabled={true} />
52+
</Tooltip>
53+
);
54+
}
55+
56+
return (
57+
<Delineator
58+
headerComponent={headerComponent}
59+
isLoading={loading}
60+
contentBoxProps={
61+
loading
62+
? undefined
63+
: {
64+
paddingLeft: 0,
65+
paddingRight: 0,
66+
paddingTop: 0,
67+
paddingBottom: 0,
68+
}
69+
}
70+
>
71+
<SnapUIRenderer
72+
snapId={snapId}
73+
interfaceId={interfaceId}
74+
isLoading={loading}
75+
useDelineator={false}
76+
/>
77+
</Delineator>
78+
);
79+
};

0 commit comments

Comments
 (0)