Skip to content

Commit

Permalink
feat(snaps): Add background color prop to Snaps UI Container component (
Browse files Browse the repository at this point in the history
#29095)

## **Description**
This PR adds `backgroundColor` property to Container component.
Background colors that can be used are predefined as: default and
alternative.
This PR deliberately disables the `backgroundColor` feature for
containers used within Transaction Insights pages (Snaps).

[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/29095?quickstart=1)

## **Related issues**
Fixes: MetaMask/snaps#2906
Related Snaps PR: MetaMask/snaps#2950

## **Manual testing steps**
1. Try installing and using a Snap with confirmation window or home
page, with `Container` in its content with `backgroundColor` defined
(e.g. `<Container backgroundColor="default">`).
2. Try the same thing on transaction insights Snaps and confirm that the
colors are not changing there.
3. Confirm that the colors are as expected on all pages.

Some source code used for testing:

- Source code used for confirmation content:
```typescript
export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
  switch (request.method) {
    case 'hello':
      return snap.request({
        method: 'snap_dialog',
        params: {
          content: (
            <Container backgroundColor="default">
              <Box>
                <Section>
                  <Row label="From">
                    <Address address="0x1234567890123456789012345678901234567890" />
                  </Row>
                  <Row
                    label="To"
                    variant="warning"
                    tooltip="This address has been deemed dangerous."
                  >
                    <Address address="0x0000000000000000000000000000000000000000" />
                  </Row>
                </Section>
                <Form name="form">
                  <Box direction="horizontal">
                    <Field label="baz">
                      <Input name="baz" placeholder="Enter something..." />
                    </Field>
                    <Field label="foo">
                      <Selector title="bar" name="test">
                        <SelectorOption value="one">
                          <Card title="test" value="test" />
                        </SelectorOption>
                      </Selector>
                    </Field>
                  </Box>
                  <Field label="something_else">
                    <Selector title="bar" name="test2">
                      <SelectorOption value="one">
                        <Card title="test" value="test" />
                      </SelectorOption>
                    </Selector>
                  </Field>
                  <Box direction="horizontal">
                    <Field label="baz2">
                      <Input name="baz2" placeholder="Enter something..." />
                    </Field>
                    <Field label="foo3">
                      <Input name="foo3" placeholder="Enter something..." />
                    </Field>
                  </Box>
                  <Box direction="horizontal">
                    <Field label="Example Dropdown">
                      <Dropdown name="example-dropdown">
                        <Option value="option1">Option 1</Option>
                        <Option value="option2">Option 2</Option>
                        <Option value="option3">Option 3</Option>
                      </Dropdown>
                    </Field>
                    <Field label="Example RadioGroup">
                      <RadioGroup name="example-radiogroup">
                        <Radio value="option1">Option 1</Radio>
                        <Radio value="option2">Option 2</Radio>
                        <Radio value="option3">Option 3</Radio>
                      </RadioGroup>
                    </Field>
                  </Box>
                  <Field label="Example Dropdown full width">
                    <Dropdown name="example-dropdown-full">
                      <Option value="option1">Option 1</Option>
                      <Option value="option2">Option 2</Option>
                      <Option value="option3">Option 3</Option>
                    </Dropdown>
                  </Field>
                  <Field label="Example RadioGroup full width">
                    <RadioGroup name="example-radiogroup-full">
                      <Radio value="option11">Option 11</Radio>
                      <Radio value="option22">Option 22</Radio>
                      <Radio value="option33">Option 33</Radio>
                    </RadioGroup>
                  </Field>
                  <Field label="foo4">
                    <Input name="foo4" placeholder="Enter something..." />
                  </Field>
                  <Box direction="horizontal">
                    <Field label="baz3">
                      <Input name="baz3" placeholder="Enter something..." />
                    </Field>
                  </Box>
                  <Box direction="horizontal">
                    <Field label="Example Checkbox">
                      <Checkbox name="example-checkbox" label="Checkbox" />
                    </Field>
                    <Field label="Example Selector">
                      <Selector
                        name="example-selector"
                        title="Choose an option"
                        value="option1"
                      >
                        <SelectorOption value="option1">
                          <Card title="Option 1" value="option1" />
                        </SelectorOption>
                        <SelectorOption value="option2">
                          <Card title="Option 2" value="option2" />
                        </SelectorOption>
                        <SelectorOption value="option3">
                          <Card title="Option 3" value="option3" />
                        </SelectorOption>
                      </Selector>
                    </Field>
                  </Box>
                  <Field label="Example FileInput 2">
                    <FileInput name="file-input-2" />
                  </Field>
                  <Field label="Example Checkbox Full field">
                    <Checkbox name="example-checkbox-full" label="Checkbox" />
                  </Field>
                  <Box direction="horizontal">
                    <Field label="baz4">
                      <Input name="baz4" placeholder="Enter something..." />
                    </Field>
                    <Field label="Example FileInput">
                      <FileInput name="file-input-1" />
                    </Field>
                  </Box>
                  <Box direction="horizontal">
                    <Field label="baz5">
                      <Input name="baz5" placeholder="Enter something..." />
                    </Field>
                    <Field label="FileInput Compact">
                      <FileInput name="file-input-3" compact={true} />
                    </Field>
                  </Box>
                  <Field label="FileInput Compact 2 full">
                    <FileInput name="file-input-4" compact={true} />
                  </Field>
                </Form>
              </Box>
            </Container>
          ),
        },
      });
    default:
      throw new Error('Method not found.');
  }
};
```

## **Screenshots/Recordings**
### **Before**
Note: Changing container colors was not possible before.
![Screenshot 2024-12-11 at 14 06
34](https://github.com/user-attachments/assets/aa7fbeb9-37ab-48f9-9613-2a76fb64960b)
![Screenshot 2024-12-11 at 14 06
59](https://github.com/user-attachments/assets/a06b620a-5755-4297-96a5-676bafb60638)

### **After**
![Screenshot 2025-01-13 at 13 34
54](https://github.com/user-attachments/assets/28486134-b5cf-42c9-ac83-eada133a00ad)
![Screenshot 2025-01-13 at 13 38
00](https://github.com/user-attachments/assets/8ecd5169-a49e-4ba0-9cc2-baa3a92a2d75)
![Screenshot 2025-01-13 at 13 38
32](https://github.com/user-attachments/assets/c674aa5c-0b52-42a7-a58a-b894dcddeb58)
![Screenshot 2025-01-13 at 13 41
11](https://github.com/user-attachments/assets/a1b5ca36-e833-40a2-84c7-06e31af57d8b)
![Screenshot 2025-01-13 at 13 43
54](https://github.com/user-attachments/assets/cf5436e1-6626-4641-933d-5a02d031bb65)
![Screenshot 2025-01-13 at 13 44
25](https://github.com/user-attachments/assets/f598d930-ebee-473a-aea8-5132ec60114c)

## **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.
  • Loading branch information
david0xd authored Jan 14, 2025
1 parent a87c1b4 commit 603bf8d
Show file tree
Hide file tree
Showing 11 changed files with 45 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export default function InsightWarnings({
isCollapsable
isCollapsed={warningState[snapId]}
boxProps={{ marginBottom: idx === lastWarningIdx ? 0 : 4 }}
contentBackgroundColor={BackgroundColor.backgroundDefault}
/>
);
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ export const SnapHomeRenderer = ({ snapId }) => {
isLoading={loading}
useDelineator={false}
useFooter
contentBackgroundColor={BackgroundColor.backgroundAlternative}
/>
)}
</Box>
Expand Down
2 changes: 2 additions & 0 deletions ui/components/app/snaps/snap-insight/snap-insight.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useSelector, useDispatch } from 'react-redux';
import { Text } from '../../../component-library';
import {
AlignItems,
BackgroundColor,
FLEX_DIRECTION,
JustifyContent,
TextAlign,
Expand Down Expand Up @@ -69,6 +70,7 @@ export const SnapInsight = ({ snapId, data }) => {
interfaceId={interfaceId}
delineatorType={DelineatorType.Insights}
isLoading={isLoading}
contentBackgroundColor={BackgroundColor.backgroundDefault}
/>
) : (
<Text
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BoxElement, JSXElement } from '@metamask/snaps-sdk/jsx';
import { ContainerElement, JSXElement } from '@metamask/snaps-sdk/jsx';
import { getJsxChildren } from '@metamask/snaps-utils';
import { mapToTemplate } from '../utils';
import {
Expand All @@ -8,7 +8,7 @@ import {
import { UIComponentFactory } from './types';
import { DEFAULT_FOOTER } from './footer';

export const container: UIComponentFactory<BoxElement> = ({
export const container: UIComponentFactory<ContainerElement> = ({
element,
useFooter,
onCancel,
Expand Down
10 changes: 9 additions & 1 deletion ui/components/app/snaps/snap-ui-renderer/components/section.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,20 @@ import { box } from './box';

export const section: UIComponentFactory<SectionElement> = ({
element,
contentBackgroundColor,
...params
}) => {
const { children, props } = box({
element,
...params,
} as unknown as UIComponentParams<BoxElement>);

// Reverse colors to improve visibility on certain backgrounds
const backgroundColor =
contentBackgroundColor === BackgroundColor.backgroundDefault
? BackgroundColor.backgroundAlternative
: BackgroundColor.backgroundDefault;

return {
element: 'Box',
children,
Expand All @@ -22,7 +30,7 @@ export const section: UIComponentFactory<SectionElement> = ({
className: 'snap-ui-renderer__section',
padding: 4,
gap: 2,
backgroundColor: BackgroundColor.backgroundDefault,
backgroundColor,
borderRadius: BorderRadius.LG,
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type UIComponentParams<T extends JSXElement> = {
placeholder?: string;
};
t: (key: string) => string;
contentBackgroundColor: string | undefined;
};

export type UIComponent = {
Expand Down
13 changes: 10 additions & 3 deletions ui/components/app/snaps/snap-ui-renderer/snap-ui-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ import { SnapInterfaceContextProvider } from '../../../../contexts/snaps';
import PulseLoader from '../../../ui/pulse-loader';
import {
AlignItems,
BackgroundColor,
BlockSize,
Display,
JustifyContent,
} from '../../../../helpers/constants/design-system';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import { mapToTemplate } from './utils';
import { mapToExtensionCompatibleColor, mapToTemplate } from './utils';

// Component that maps Snaps UI JSON format to MetaMask Template Renderer format
const SnapUIRendererComponent = ({
Expand Down Expand Up @@ -69,6 +70,11 @@ const SnapUIRendererComponent = ({
[inputValue, onInputChange, placeholder, isPrompt],
);

const backgroundColor =
contentBackgroundColor ??
mapToExtensionCompatibleColor(content?.props?.backgroundColor) ??
BackgroundColor.backgroundAlternative;

const sections = useMemo(
() =>
content &&
Expand All @@ -79,8 +85,9 @@ const SnapUIRendererComponent = ({
useFooter,
promptLegacyProps,
t,
contentBackgroundColor: backgroundColor,
}),
[content, onCancel, useFooter, promptLegacyProps, t],
[content, onCancel, useFooter, promptLegacyProps, t, backgroundColor],
);

if (isLoading || !content) {
Expand Down Expand Up @@ -130,7 +137,7 @@ const SnapUIRendererComponent = ({
<Box
className="snap-ui-renderer__content"
height={BlockSize.Full}
backgroundColor={contentBackgroundColor}
backgroundColor={backgroundColor}
style={{
overflowY: 'auto',
marginBottom: useFooter ? '80px' : '0',
Expand Down
16 changes: 16 additions & 0 deletions ui/components/app/snaps/snap-ui-renderer/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { sha256 } from '@noble/hashes/sha256';
import { NonEmptyArray, bytesToHex, remove0x } from '@metamask/utils';
import { unescape as unescapeEntities } from 'he';
import { ChangeEvent as ReactChangeEvent } from 'react';
import { BackgroundColor } from '../../../../helpers/constants/design-system';
import { COMPONENT_MAPPING } from './components';
import { UIComponent } from './components/types';

Expand All @@ -20,6 +21,7 @@ export type MapToTemplateParams = {
placeholder?: string;
};
t?: (key: string) => string;
contentBackgroundColor?: string | undefined;
};

/**
Expand Down Expand Up @@ -140,3 +142,17 @@ export const FIELD_ELEMENT_TYPES = [
export const getPrimaryChildElementIndex = (children: JSXElement[]) => {
return children.findIndex((c) => FIELD_ELEMENT_TYPES.includes(c.type));
};

/**
* Map Snap custom color to extension compatible color.
*
* @param color - Snap custom color.
* @returns String, representing color from design system.
*/
export const mapToExtensionCompatibleColor = (color: string) => {
const backgroundColorMapping: { [key: string]: string | undefined } = {
default: BackgroundColor.backgroundAlternative, // For Snaps, the default background color is the Alternative
alternative: BackgroundColor.backgroundDefault,
};
return color ? backgroundColorMapping[color] : undefined;
};
2 changes: 2 additions & 0 deletions ui/components/app/snaps/snap-ui-selector/index.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.snap-ui-renderer {
&__selector {
width: 100%;
border: 1px solid var(--color-border-muted);

& > span:first-child {
width: 100%;
Expand All @@ -12,6 +13,7 @@

&:hover {
background-color: var(--color-background-alternative-hover);
border-color: var(--color-border-default);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
TextColor,
TextVariant,
FontWeight,
BackgroundColor,
} from '../../../../../../helpers/constants/design-system';
import { useI18nContext } from '../../../../../../hooks/useI18nContext';
import { getSnapMetadata } from '../../../../../../selectors';
Expand Down Expand Up @@ -75,6 +76,7 @@ export const SnapInsight: React.FunctionComponent<SnapInsightProps> = ({
interfaceId={interfaceId}
isLoading={loading}
useDelineator={false}
contentBackgroundColor={BackgroundColor.backgroundDefault}
/>
</Delineator>
);
Expand Down
2 changes: 0 additions & 2 deletions ui/pages/confirmations/confirmation/confirmation.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import { SnapUIRenderer } from '../../../components/app/snaps/snap-ui-renderer';
import { SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES } from '../../../../shared/constants/app';
///: END:ONLY_INCLUDE_IF
import { DAY } from '../../../../shared/constants/time';
import { BackgroundColor } from '../../../helpers/constants/design-system';
import { Nav } from '../components/confirm/nav';
import { ConfirmContextProvider } from '../context/confirm';
import { useConfirmationNavigation } from '../hooks/useConfirmationNavigation';
Expand Down Expand Up @@ -523,7 +522,6 @@ export default function ConfirmationPage({
useDelineator={false}
onCancel={handleSnapDialogCancel}
useFooter={isSnapDefaultDialog}
contentBackgroundColor={BackgroundColor.backgroundAlternative}
/>
) : (
<MetaMaskTemplateRenderer sections={templatedValues.content} />
Expand Down

0 comments on commit 603bf8d

Please sign in to comment.