diff --git a/packages/examples/packages/browserify-plugin/snap.manifest.json b/packages/examples/packages/browserify-plugin/snap.manifest.json
index 712aba8313..781b5057a7 100644
--- a/packages/examples/packages/browserify-plugin/snap.manifest.json
+++ b/packages/examples/packages/browserify-plugin/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
- "shasum": "zWAvAKOxzUuZWjb75gYJw+izGqyBRcXoy2ocUCKMyGk=",
+ "shasum": "WKxKUU91g512MKJ5lFiKEXBIxwyFmiifC1XTXXYfIWg=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/examples/packages/browserify/snap.manifest.json b/packages/examples/packages/browserify/snap.manifest.json
index f7b044698a..0659550b20 100644
--- a/packages/examples/packages/browserify/snap.manifest.json
+++ b/packages/examples/packages/browserify/snap.manifest.json
@@ -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",
diff --git a/packages/snaps-rpc-methods/src/permitted/createInterface.test.tsx b/packages/snaps-rpc-methods/src/permitted/createInterface.test.tsx
index 3e99bf7129..29ec7d0b25 100644
--- a/packages/snaps-rpc-methods/src/permitted/createInterface.test.tsx
+++ b/packages/snaps-rpc-methods/src/permitted/createInterface.test.tsx
@@ -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,
diff --git a/packages/snaps-sdk/src/jsx/components/Banner.test.tsx b/packages/snaps-sdk/src/jsx/components/Banner.test.tsx
new file mode 100644
index 0000000000..d80c07fcdd
--- /dev/null
+++ b/packages/snaps-sdk/src/jsx/components/Banner.test.tsx
@@ -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 = (
+
+ Example test banner.
+ foo
+
+
+ foo
+ bar
+
+ );
+
+ 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,
+ },
+ ],
+ },
+ });
+ });
+});
diff --git a/packages/snaps-sdk/src/jsx/components/Banner.ts b/packages/snaps-sdk/src/jsx/components/Banner.ts
new file mode 100644
index 0000000000..a558d90b17
--- /dev/null
+++ b/packages/snaps-sdk/src/jsx/components/Banner.ts
@@ -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
+ *
+ * Here is the banner content!
+ *
+ */
+export const Banner = createSnapComponent(TYPE);
+
+/**
+ * A Banner element.
+ *
+ * @see Banner
+ */
+export type BannerElement = ReturnType;
diff --git a/packages/snaps-sdk/src/jsx/components/index.ts b/packages/snaps-sdk/src/jsx/components/index.ts
index bc64836bb2..d26cbcd338 100644
--- a/packages/snaps-sdk/src/jsx/components/index.ts
+++ b/packages/snaps-sdk/src/jsx/components/index.ts
@@ -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';
@@ -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.
@@ -63,4 +65,5 @@ export type JSXElement =
| SectionElement
| SpinnerElement
| TextElement
- | TooltipElement;
+ | TooltipElement
+ | BannerElement;
diff --git a/packages/snaps-sdk/src/jsx/validation.test.tsx b/packages/snaps-sdk/src/jsx/validation.test.tsx
index 23bccc91c2..e3e2b086d8 100644
--- a/packages/snaps-sdk/src/jsx/validation.test.tsx
+++ b/packages/snaps-sdk/src/jsx/validation.test.tsx
@@ -33,6 +33,7 @@ import {
SelectorOption,
Section,
Avatar,
+ Banner,
} from './components';
import {
AddressStruct,
@@ -70,6 +71,7 @@ import {
SelectorStruct,
SectionStruct,
AvatarStruct,
+ BannerStruct,
} from './validation';
describe('KeyStruct', () => {
@@ -1571,3 +1573,32 @@ describe('assertJSXElement', () => {
}).not.toThrow();
});
});
+
+describe('BannerStruct', () => {
+ it.each([
+
+ bar
+ ,
+ ])(`validates a banner element`, (value) => {
+ expect(is(value, BannerStruct)).toBe(true);
+ });
+
+ it.each([
+ 'foo',
+ 42,
+ null,
+ undefined,
+ {},
+ [],
+ // @ts-expect-error - Invalid props.
+ ,
+ // @ts-expect-error - Invalid props.
+ foo,
+ // @ts-expect-error - Invalid props.
+ } severity="info">
+ foo
+ ,
+ ])('does not validate "%p"', (value) => {
+ expect(is(value, BannerStruct)).toBe(false);
+ });
+});
diff --git a/packages/snaps-sdk/src/jsx/validation.ts b/packages/snaps-sdk/src/jsx/validation.ts
index 99a1ce24fb..1be3f4c660 100644
--- a/packages/snaps-sdk/src/jsx/validation.ts
+++ b/packages/snaps-sdk/src/jsx/validation.ts
@@ -19,6 +19,7 @@ import {
tuple,
refine,
assign,
+ union,
} from '@metamask/superstruct';
import {
CaipAccountIdStruct,
@@ -82,6 +83,7 @@ import {
type SectionElement,
type SelectorElement,
type SelectorOptionElement,
+ type BannerElement,
IconName,
} from './components';
@@ -769,6 +771,27 @@ export const TooltipStruct: Describe = element('Tooltip', {
content: TooltipContentStruct,
});
+/**
+ * A struct for the {@link BannerElement} type.
+ */
+export const BannerStruct: Describe = 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.
*/
@@ -824,6 +847,7 @@ export const BoxChildStruct = typedUnion([
SelectorStruct,
SectionStruct,
AvatarStruct,
+ BannerStruct,
]);
/**
@@ -889,6 +913,7 @@ export const JSXElementStruct: Describe = typedUnion([
SelectorOptionStruct,
SectionStruct,
AvatarStruct,
+ BannerStruct,
]);
/**