Skip to content

Commit

Permalink
Add Banner UI component (#2961)
Browse files Browse the repository at this point in the history
This PR adds specification for Banner UI component.

![Screenshot 2024-12-17 at 11 43
18](https://github.com/user-attachments/assets/562417fb-b0cb-49be-a68f-7e405e0e5f37)
  • Loading branch information
david0xd authored Dec 17, 2024
1 parent 967360c commit e1621d8
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "zWAvAKOxzUuZWjb75gYJw+izGqyBRcXoy2ocUCKMyGk=",
"shasum": "WKxKUU91g512MKJ5lFiKEXBIxwyFmiifC1XTXXYfIWg=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/examples/packages/browserify/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "i5/9SEgHydVQotkWZ7ErpPhOI2N7Cg3U+lbR5/ApQI4=",
"shasum": "3ox9QePlOph93lUAO39++bXnyMmaiRoisG3mbf9j2dI=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ describe('snap_createInterface', () => {
error: {
code: -32602,
message:
'Invalid params: At path: ui -- Expected type to be one of: "Address", "Bold", "Box", "Button", "Copyable", "Divider", "Dropdown", "RadioGroup", "Field", "FileInput", "Form", "Heading", "Input", "Image", "Italic", "Link", "Row", "Spinner", "Text", "Tooltip", "Checkbox", "Card", "Icon", "Selector", "Section", "Avatar", "Container", but received: undefined.',
'Invalid params: At path: ui -- Expected type to be one of: "Address", "Bold", "Box", "Button", "Copyable", "Divider", "Dropdown", "RadioGroup", "Field", "FileInput", "Form", "Heading", "Input", "Image", "Italic", "Link", "Row", "Spinner", "Text", "Tooltip", "Checkbox", "Card", "Icon", "Selector", "Section", "Avatar", "Banner", "Container", but received: undefined.',
stack: expect.any(String),
},
id: 1,
Expand Down
79 changes: 79 additions & 0 deletions packages/snaps-sdk/src/jsx/components/Banner.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Banner } from './Banner';
import { Button } from './form/Button';
import { Bold } from './formatting/Bold';
import { Italic } from './formatting/Italic';
import { Icon } from './Icon';
import { Link } from './Link';
import { Text } from './Text';

describe('Banner', () => {
it('renders a banner component', () => {
const result = (
<Banner title="Test Banner" severity="info">
<Text>Example test banner.</Text>
<Link href="https://example.com">foo</Link>
<Button type="button">foo</Button>
<Icon name="arrow-left" color="primary" size="md" />
<Bold>foo</Bold>
<Italic>bar</Italic>
</Banner>
);

expect(result).toStrictEqual({
type: 'Banner',
key: null,
props: {
title: 'Test Banner',
severity: 'info',
children: [
{
type: 'Text',
props: {
children: 'Example test banner.',
},
key: null,
},
{
type: 'Link',
props: {
href: 'https://example.com',
children: 'foo',
},
key: null,
},
{
type: 'Button',
props: {
type: 'button',
children: 'foo',
},
key: null,
},
{
type: 'Icon',
props: {
name: 'arrow-left',
color: 'primary',
size: 'md',
},
key: null,
},
{
type: 'Bold',
props: {
children: 'foo',
},
key: null,
},
{
type: 'Italic',
props: {
children: 'bar',
},
key: null,
},
],
},
});
});
});
53 changes: 53 additions & 0 deletions packages/snaps-sdk/src/jsx/components/Banner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { createSnapComponent, type SnapsChildren } from '../component';
import type { ButtonElement } from './form/Button';
import type { StandardFormattingElement } from './formatting';
import type { IconElement } from './Icon';
import type { LinkElement } from './Link';
import type { TextElement } from './Text';

/**
* Types of children components that can be used with Banner.
*/
export type BannerChildren = SnapsChildren<
| TextElement
| StandardFormattingElement
| LinkElement
| IconElement
| ButtonElement
>;

/**
* The props of the {@link Banner} component.
*
* @param children - The content to display in the banner.
* @param title - Title of the banner.
* @param severity - Severity level of the banner.
*/
export type BannerProps = {
children: BannerChildren;
title: string;
severity: 'danger' | 'info' | 'success' | 'warning';
};

const TYPE = 'Banner';

/**
* A Banner component, which is used to display custom banner alerts.
*
* @param props - The props of the component.
* @param props.children - The content to display in the banner.
* @param props.title - Title of the banner.
* @param props.severity - Severity level of the banner.
* @example
* <Banner title="Success banner" severity="success">
* <Text>Here is the banner content!</Text>
* </Banner>
*/
export const Banner = createSnapComponent<BannerProps, typeof TYPE>(TYPE);

/**
* A Banner element.
*
* @see Banner
*/
export type BannerElement = ReturnType<typeof Banner>;
5 changes: 4 additions & 1 deletion packages/snaps-sdk/src/jsx/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { AddressElement } from './Address';
import type { AvatarElement } from './Avatar';
import type { BannerElement } from './Banner';
import type { BoxElement } from './Box';
import type { CardElement } from './Card';
import type { ContainerElement } from './Container';
Expand Down Expand Up @@ -39,6 +40,7 @@ export * from './Tooltip';
export * from './Footer';
export * from './Container';
export * from './Section';
export * from './Banner';

/**
* A built-in JSX element, which can be used in a Snap user interface.
Expand All @@ -63,4 +65,5 @@ export type JSXElement =
| SectionElement
| SpinnerElement
| TextElement
| TooltipElement;
| TooltipElement
| BannerElement;
31 changes: 31 additions & 0 deletions packages/snaps-sdk/src/jsx/validation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
SelectorOption,
Section,
Avatar,
Banner,
} from './components';
import {
AddressStruct,
Expand Down Expand Up @@ -70,6 +71,7 @@ import {
SelectorStruct,
SectionStruct,
AvatarStruct,
BannerStruct,
} from './validation';

describe('KeyStruct', () => {
Expand Down Expand Up @@ -1571,3 +1573,32 @@ describe('assertJSXElement', () => {
}).not.toThrow();
});
});

describe('BannerStruct', () => {
it.each([
<Banner title="foo" severity="info">
<Text>bar</Text>
</Banner>,
])(`validates a banner element`, (value) => {
expect(is(value, BannerStruct)).toBe(true);
});

it.each([
'foo',
42,
null,
undefined,
{},
[],
// @ts-expect-error - Invalid props.
<Banner />,
// @ts-expect-error - Invalid props.
<Banner foo="bar">foo</Banner>,
// @ts-expect-error - Invalid props.
<Banner title={<Copyable value="bar" />} severity="info">
<Text>foo</Text>
</Banner>,
])('does not validate "%p"', (value) => {
expect(is(value, BannerStruct)).toBe(false);
});
});
25 changes: 25 additions & 0 deletions packages/snaps-sdk/src/jsx/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
tuple,
refine,
assign,
union,
} from '@metamask/superstruct';
import {
CaipAccountIdStruct,
Expand Down Expand Up @@ -82,6 +83,7 @@ import {
type SectionElement,
type SelectorElement,
type SelectorOptionElement,
type BannerElement,
IconName,
} from './components';

Expand Down Expand Up @@ -769,6 +771,27 @@ export const TooltipStruct: Describe<TooltipElement> = element('Tooltip', {
content: TooltipContentStruct,
});

/**
* A struct for the {@link BannerElement} type.
*/
export const BannerStruct: Describe<BannerElement> = element('Banner', {
children: children([
TextStruct,
LinkStruct,
IconStruct,
ButtonStruct,
BoldStruct,
ItalicStruct,
]),
title: string(),
severity: union([
literal('danger'),
literal('info'),
literal('success'),
literal('warning'),
]),
});

/**
* A struct for the {@link RowElement} type.
*/
Expand Down Expand Up @@ -824,6 +847,7 @@ export const BoxChildStruct = typedUnion([
SelectorStruct,
SectionStruct,
AvatarStruct,
BannerStruct,
]);

/**
Expand Down Expand Up @@ -889,6 +913,7 @@ export const JSXElementStruct: Describe<JSXElement> = typedUnion([
SelectorOptionStruct,
SectionStruct,
AvatarStruct,
BannerStruct,
]);

/**
Expand Down

0 comments on commit e1621d8

Please sign in to comment.