Skip to content

Commit ecc52a3

Browse files
committed
Add Banner UI component
1 parent 967360c commit ecc52a3

File tree

5 files changed

+185
-1
lines changed

5 files changed

+185
-1
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { Banner } from './Banner';
2+
import { Button } from './form/Button';
3+
import { Bold } from './formatting/Bold';
4+
import { Italic } from './formatting/Italic';
5+
import { Icon } from './Icon';
6+
import { Link } from './Link';
7+
import { Text } from './Text';
8+
9+
describe('Banner', () => {
10+
it('renders a banner component', () => {
11+
const result = (
12+
<Banner title="Test Banner" severity="info">
13+
<Text>Example test banner.</Text>
14+
<Link href="https://example.com">foo</Link>
15+
<Button type="button">foo</Button>
16+
<Icon name="arrow-left" color="primary" size="md" />
17+
<Bold>foo</Bold>
18+
<Italic>bar</Italic>
19+
</Banner>
20+
);
21+
22+
expect(result).toStrictEqual({
23+
type: 'Banner',
24+
key: null,
25+
props: {
26+
title: 'Test Banner',
27+
severity: 'info',
28+
children: [
29+
{
30+
type: 'Text',
31+
props: {
32+
children: 'Example test banner.',
33+
},
34+
key: null,
35+
},
36+
{
37+
type: 'Link',
38+
props: {
39+
href: 'https://example.com',
40+
children: 'foo',
41+
},
42+
key: null,
43+
},
44+
{
45+
type: 'Button',
46+
props: {
47+
type: 'button',
48+
children: 'foo',
49+
},
50+
key: null,
51+
},
52+
{
53+
type: 'Icon',
54+
props: {
55+
name: 'arrow-left',
56+
color: 'primary',
57+
size: 'md',
58+
},
59+
key: null,
60+
},
61+
{
62+
type: 'Bold',
63+
props: {
64+
children: 'foo',
65+
},
66+
key: null,
67+
},
68+
{
69+
type: 'Italic',
70+
props: {
71+
children: 'bar',
72+
},
73+
key: null,
74+
},
75+
],
76+
},
77+
});
78+
});
79+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { createSnapComponent, type SnapsChildren } from '../component';
2+
import type { ButtonElement } from './form/Button';
3+
import type { StandardFormattingElement } from './formatting';
4+
import type { IconElement } from './Icon';
5+
import type { LinkElement } from './Link';
6+
import type { TextElement } from './Text';
7+
8+
/**
9+
* Types of children components that can be used with Banner.
10+
*/
11+
export type BannerChildren = SnapsChildren<
12+
| TextElement
13+
| StandardFormattingElement
14+
| LinkElement
15+
| IconElement
16+
| ButtonElement
17+
>;
18+
19+
/**
20+
* The props of the {@link Banner} component.
21+
*/
22+
export type BannerProps = {
23+
children: BannerChildren;
24+
title: string;
25+
severity: 'danger' | 'info' | 'success' | 'warning';
26+
};
27+
28+
const TYPE = 'Banner';
29+
30+
/**
31+
* A Banner component, which is used to display custom banner alerts.
32+
*
33+
* @param props - The props of the component.
34+
* @example
35+
* <Banner title="Success banner" severity="success">
36+
* <Text>Here is the banner content!</Text>
37+
* </Banner>
38+
*/
39+
export const Banner = createSnapComponent<BannerProps, typeof TYPE>(TYPE);
40+
41+
/**
42+
* A Banner element.
43+
*
44+
* @see Banner
45+
*/
46+
export type BannerElement = ReturnType<typeof Banner>;

packages/snaps-sdk/src/jsx/components/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { AddressElement } from './Address';
22
import type { AvatarElement } from './Avatar';
3+
import type { BannerElement } from './Banner';
34
import type { BoxElement } from './Box';
45
import type { CardElement } from './Card';
56
import type { ContainerElement } from './Container';
@@ -39,6 +40,7 @@ export * from './Tooltip';
3940
export * from './Footer';
4041
export * from './Container';
4142
export * from './Section';
43+
export * from './Banner';
4244

4345
/**
4446
* A built-in JSX element, which can be used in a Snap user interface.
@@ -63,4 +65,5 @@ export type JSXElement =
6365
| SectionElement
6466
| SpinnerElement
6567
| TextElement
66-
| TooltipElement;
68+
| TooltipElement
69+
| BannerElement;

packages/snaps-sdk/src/jsx/validation.test.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
SelectorOption,
3434
Section,
3535
Avatar,
36+
Banner,
3637
} from './components';
3738
import {
3839
AddressStruct,
@@ -70,6 +71,7 @@ import {
7071
SelectorStruct,
7172
SectionStruct,
7273
AvatarStruct,
74+
BannerStruct,
7375
} from './validation';
7476

7577
describe('KeyStruct', () => {
@@ -1571,3 +1573,32 @@ describe('assertJSXElement', () => {
15711573
}).not.toThrow();
15721574
});
15731575
});
1576+
1577+
describe('BannerStruct', () => {
1578+
it.each([
1579+
<Banner title="foo" severity="info">
1580+
<Text>bar</Text>
1581+
</Banner>,
1582+
])(`validates a banner element`, (value) => {
1583+
expect(is(value, BannerStruct)).toBe(true);
1584+
});
1585+
1586+
it.each([
1587+
'foo',
1588+
42,
1589+
null,
1590+
undefined,
1591+
{},
1592+
[],
1593+
// @ts-expect-error - Invalid props.
1594+
<Banner />,
1595+
// @ts-expect-error - Invalid props.
1596+
<Banner foo="bar">foo</Banner>,
1597+
// @ts-expect-error - Invalid props.
1598+
<Banner title={<Copyable value="bar" />} severity="info">
1599+
<Text>foo</Text>
1600+
</Banner>,
1601+
])('does not validate "%p"', (value) => {
1602+
expect(is(value, BannerStruct)).toBe(false);
1603+
});
1604+
});

packages/snaps-sdk/src/jsx/validation.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
tuple,
2020
refine,
2121
assign,
22+
union,
2223
} from '@metamask/superstruct';
2324
import {
2425
CaipAccountIdStruct,
@@ -82,6 +83,7 @@ import {
8283
type SectionElement,
8384
type SelectorElement,
8485
type SelectorOptionElement,
86+
type BannerElement,
8587
IconName,
8688
} from './components';
8789

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

774+
/**
775+
* A struct for the {@link BannerElement} type.
776+
*/
777+
export const BannerStruct: Describe<BannerElement> = element('Banner', {
778+
children: children([
779+
TextStruct,
780+
LinkStruct,
781+
IconStruct,
782+
ButtonStruct,
783+
BoldStruct,
784+
ItalicStruct,
785+
]),
786+
title: string(),
787+
severity: union([
788+
literal('danger'),
789+
literal('info'),
790+
literal('success'),
791+
literal('warning'),
792+
]),
793+
});
794+
772795
/**
773796
* A struct for the {@link RowElement} type.
774797
*/
@@ -824,6 +847,7 @@ export const BoxChildStruct = typedUnion([
824847
SelectorStruct,
825848
SectionStruct,
826849
AvatarStruct,
850+
BannerStruct,
827851
]);
828852

829853
/**
@@ -889,6 +913,7 @@ export const JSXElementStruct: Describe<JSXElement> = typedUnion([
889913
SelectorOptionStruct,
890914
SectionStruct,
891915
AvatarStruct,
916+
BannerStruct,
892917
]);
893918

894919
/**

0 commit comments

Comments
 (0)