Skip to content

Commit

Permalink
feat: Integrate Snaps into the redesigned confirmations (#26435)
Browse files Browse the repository at this point in the history
<!--
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)
  • Loading branch information
FrederikBolding committed Aug 22, 2024
1 parent d2d48d2 commit d81d69b
Show file tree
Hide file tree
Showing 11 changed files with 358 additions and 12 deletions.
2 changes: 1 addition & 1 deletion app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 23 additions & 8 deletions ui/components/ui/delineator/delineator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const ExpandableIcon = ({ isExpanded }: { isExpanded: boolean }) => {
<Icon
name={isExpanded ? IconName.ArrowUp : IconName.ArrowDown}
size={IconSize.Sm}
color={IconColor.iconMuted}
color={IconColor.primaryDefault}
/>
);
};
Expand All @@ -49,14 +49,16 @@ const Header = ({
isCollapsible,
isExpanded,
isLoading,
isDisabled,
onHeaderClick,
type,
}: {
headerComponent: DelineatorProps['headerComponent'];
iconName: IconName;
iconName?: IconName;
isCollapsible: boolean;
isExpanded: boolean;
isLoading: boolean;
isDisabled: boolean;
onHeaderClick: () => void;
type?: DelineatorType;
}) => {
Expand All @@ -67,18 +69,19 @@ const Header = ({
delineator__header: true,
'delineator__header--expanded': isExpanded,
'delineator__header--loading': isLoading,
'delineator__header--disabled': isDisabled,
})}
display={Display.Flex}
alignItems={AlignItems.center}
justifyContent={JustifyContent.spaceBetween}
paddingTop={2}
paddingRight={4}
paddingBottom={2}
paddingBottom={isExpanded ? 0 : 2}
paddingLeft={4}
onClick={onHeaderClick}
>
<Box display={Display.Flex} alignItems={AlignItems.center}>
<AvatarIcon iconName={iconName} {...iconProps} />
{iconName && <AvatarIcon iconName={iconName} {...iconProps} />}
{overrideTextComponentColorByType({
component: headerComponent,
type,
Expand All @@ -89,14 +92,21 @@ const Header = ({
</Box>
);
};
const Content = ({ children }: { children: React.ReactNode }) => {
const Content = ({
children,
contentBoxProps,
}: {
children: React.ReactNode;
contentBoxProps: DelineatorProps['contentBoxProps'];
}) => {
return (
<Box
paddingTop={2}
paddingRight={4}
paddingBottom={4}
paddingLeft={4}
flexDirection={FlexDirection.Column}
{...contentBoxProps}
>
{children}
</Box>
Expand Down Expand Up @@ -131,21 +141,23 @@ export const Delineator: React.FC<DelineatorProps> = ({
isCollapsible = true,
isExpanded: isExpandedProp,
isLoading = false,
isDisabled = false,
onExpandChange,
type,
wrapperBoxProps,
contentBoxProps,
}) => {
const [isExpanded, setIsExpanded] = useState(isExpandedProp || false);
const shouldShowContent = !isCollapsible || (isCollapsible && isExpanded);

const handleHeaderClick = useCallback(() => {
if (isLoading || !isCollapsible) {
if (isDisabled || isLoading || !isCollapsible) {
return;
}
const newExpandedState = !isExpanded;
onExpandChange?.(newExpandedState);
setIsExpanded(newExpandedState);
}, [isLoading, isCollapsible, isExpanded, onExpandChange]);
}, [isLoading, isCollapsible, isExpanded, isDisabled, onExpandChange]);

return (
<Container wrapperBoxProps={wrapperBoxProps}>
Expand All @@ -155,10 +167,13 @@ export const Delineator: React.FC<DelineatorProps> = ({
isCollapsible={isCollapsible}
isExpanded={isExpanded}
isLoading={isLoading}
isDisabled={isDisabled}
onHeaderClick={handleHeaderClick}
type={type}
/>
{shouldShowContent && !isLoading && <Content>{children}</Content>}
{shouldShowContent && !isLoading && (
<Content contentBoxProps={contentBoxProps}>{children}</Content>
)}
</Container>
);
};
4 changes: 3 additions & 1 deletion ui/components/ui/delineator/delineator.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { Box, IconName, Text } from '../../component-library';
export type DelineatorProps = {
children?: React.ReactNode;
headerComponent: React.ReactElement<typeof Text>;
iconName: IconName;
iconName?: IconName;
isCollapsible?: boolean;
isExpanded?: boolean;
isLoading?: boolean;
isDisabled?: boolean;
onExpandChange?: (isExpanded: boolean) => void;
type?: DelineatorType;
wrapperBoxProps?: React.ComponentProps<typeof Box>;
contentBoxProps?: React.ComponentProps<typeof Box>;
};

export enum DelineatorType {
Expand Down
5 changes: 5 additions & 0 deletions ui/components/ui/delineator/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,10 @@
&--loading {
cursor: default;
}

&--disabled {
cursor: default;
opacity: 0.5;
}
}
}
2 changes: 1 addition & 1 deletion ui/components/ui/delineator/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const getTextColorByType = (type?: DelineatorType) => {
case DelineatorType.Error:
return TextColor.errorDefault;
default:
return TextColor.textAlternative;
return TextColor.textDefault;
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { ReactComponentLike } from 'prop-types';
import { useSelector } from 'react-redux';

import { currentConfirmationSelector } from '../../../selectors';
import { SnapsSection } from '../snaps/snaps-section';

// Components to be plugged into confirmation page can be added to the array below
const pluggedInSections: ReactComponentLike[] = [];
const pluggedInSections: ReactComponentLike[] = [SnapsSection];

const PluggableSection = () => {
const currentConfirmation = useSelector(currentConfirmationSelector);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`SnapsSection renders section for typed sign request 1`] = `
<div>
<div
class="mm-box mm-box--margin-bottom-4 mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-column"
>
<div
class="mm-box delineator__wrapper mm-box--display-flex mm-box--flex-direction-column mm-box--background-color-background-default mm-box--rounded-lg"
>
<div
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"
>
<div
class="mm-box mm-box--display-flex mm-box--align-items-center"
>
<p
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
>
<span>
Insights from
<span
class="mm-box mm-text mm-text--inherit mm-text--font-weight-medium mm-box--color-inherit"
>
BIP-32 Test Snap
</span>
</span>
</p>
</div>
<span
class="mm-box mm-icon mm-icon--size-sm mm-box--display-inline-block mm-box--color-primary-default"
style="mask-image: url('./images/icons/arrow-up.svg');"
/>
</div>
<div
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"
>
<div
class="mm-box snap-ui-renderer__content mm-box--height-full"
>
<div
class="box snap-ui-renderer__container box--display-flex box--flex-direction-column box--height-full"
>
<p
class="mm-box mm-text snap-ui-renderer__text mm-text--body-md mm-text--overflow-wrap-anywhere mm-box--color-inherit"
>
Hello world again!
</p>
</div>
</div>
</div>
</div>
</div>
</div>
`;

exports[`SnapsSection renders section personal sign request 1`] = `
<div>
<div
class="mm-box mm-box--margin-bottom-4 mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-column"
>
<div
class="mm-box delineator__wrapper mm-box--display-flex mm-box--flex-direction-column mm-box--background-color-background-default mm-box--rounded-lg"
>
<div
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"
>
<div
class="mm-box mm-box--display-flex mm-box--align-items-center"
>
<p
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
>
<span>
Insights from
<span
class="mm-box mm-text mm-text--inherit mm-text--font-weight-medium mm-box--color-inherit"
>
BIP-32 Test Snap
</span>
</span>
</p>
</div>
<span
class="mm-box mm-icon mm-icon--size-sm mm-box--display-inline-block mm-box--color-primary-default"
style="mask-image: url('./images/icons/arrow-up.svg');"
/>
</div>
<div
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"
>
<div
class="mm-box snap-ui-renderer__content mm-box--height-full"
>
<div
class="box snap-ui-renderer__container box--display-flex box--flex-direction-column box--height-full"
>
<p
class="mm-box mm-text snap-ui-renderer__text mm-text--body-md mm-text--overflow-wrap-anywhere mm-box--color-inherit"
>
Hello world!
</p>
</div>
</div>
</div>
</div>
</div>
</div>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './snaps-section';
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { SnapUIRenderer } from '../../../../../../components/app/snaps/snap-ui-renderer';
import { Delineator } from '../../../../../../components/ui/delineator';
import { Text } from '../../../../../../components/component-library';
import {
TextColor,
TextVariant,
FontWeight,
} from '../../../../../../helpers/constants/design-system';
import { useI18nContext } from '../../../../../../hooks/useI18nContext';
import { getSnapMetadata } from '../../../../../../selectors';
import Tooltip from '../../../../../../components/ui/tooltip';

export type SnapInsightProps = {
snapId: string;
interfaceId: string;
loading: boolean;
};

export const SnapInsight: React.FunctionComponent<SnapInsightProps> = ({
snapId,
interfaceId,
loading,
}) => {
const t = useI18nContext();
const { name: snapName } = useSelector((state) =>
/* @ts-expect-error wrong type on selector. */
getSnapMetadata(state, snapId),
);

const headerComponent = (
<Text>
{t('insightsFromSnap', [
<Text
fontWeight={FontWeight.Medium}
variant={TextVariant.inherit}
color={TextColor.inherit}
>
{snapName}
</Text>,
])}
</Text>
);

const hasNoInsight = !loading && !interfaceId;

if (hasNoInsight) {
return (
<Tooltip position="top" title={t('snapsNoInsight')}>
<Delineator headerComponent={headerComponent} isDisabled={true} />
</Tooltip>
);
}

return (
<Delineator
headerComponent={headerComponent}
isLoading={loading}
contentBoxProps={
loading
? undefined
: {
paddingLeft: 0,
paddingRight: 0,
paddingTop: 0,
paddingBottom: 0,
}
}
>
<SnapUIRenderer
snapId={snapId}
interfaceId={interfaceId}
isLoading={loading}
useDelineator={false}
/>
</Delineator>
);
};
Loading

0 comments on commit d81d69b

Please sign in to comment.