diff --git a/packages/examples/packages/browserify-plugin/snap.manifest.json b/packages/examples/packages/browserify-plugin/snap.manifest.json
index 0d829905dc..ac37af9969 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": "ssGPi4fxyZSvKOMXbVdOa/vnTx0qrq6WZWCUV91dLqo=",
+ "shasum": "BiCtwmtmQRs6IrssjgH8PXZWH7afx75+RKXMnl9KDZ8=",
"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 1376a8e7eb..e30bb3119f 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": "+342Ghzfo9UpTxJgqIPOieHvqRTnf4h00s8r0DpbozY=",
+ "shasum": "9K88gT7CbGCXTc/Qx63zbS91VQtHTkFFXWLjyXoM6YU=",
"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 29ec7d0b25..1403fa95a3 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", "Banner", "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", "Skeleton", "Container", but received: undefined.',
stack: expect.any(String),
},
id: 1,
diff --git a/packages/snaps-sdk/src/internals/structs.test.ts b/packages/snaps-sdk/src/internals/structs.test.ts
index 2e7a007274..0c20e92ce6 100644
--- a/packages/snaps-sdk/src/internals/structs.test.ts
+++ b/packages/snaps-sdk/src/internals/structs.test.ts
@@ -66,7 +66,7 @@ describe('typedUnion', () => {
const result = validate(Text({}), unionStruct);
expect(result[0]?.message).toBe(
- 'At path: props.children -- Expected type to be one of: "Bold", "Italic", "Link", "Icon", but received: undefined',
+ 'At path: props.children -- Expected type to be one of: "Bold", "Italic", "Link", "Icon", "Skeleton", but received: undefined',
);
});
@@ -75,7 +75,7 @@ describe('typedUnion', () => {
const result = validate(Text({}), nestedUnionStruct);
expect(result[0]?.message).toBe(
- 'At path: props.children -- Expected type to be one of: "Bold", "Italic", "Link", "Icon", but received: undefined',
+ 'At path: props.children -- Expected type to be one of: "Bold", "Italic", "Link", "Icon", "Skeleton", but received: undefined',
);
});
diff --git a/packages/snaps-sdk/src/jsx/components/Banner.ts b/packages/snaps-sdk/src/jsx/components/Banner.ts
index a558d90b17..91cbe9cf66 100644
--- a/packages/snaps-sdk/src/jsx/components/Banner.ts
+++ b/packages/snaps-sdk/src/jsx/components/Banner.ts
@@ -3,6 +3,7 @@ import type { ButtonElement } from './form/Button';
import type { StandardFormattingElement } from './formatting';
import type { IconElement } from './Icon';
import type { LinkElement } from './Link';
+import type { SkeletonElement } from './Skeleton';
import type { TextElement } from './Text';
/**
@@ -14,6 +15,7 @@ export type BannerChildren = SnapsChildren<
| LinkElement
| IconElement
| ButtonElement
+ | SkeletonElement
>;
/**
diff --git a/packages/snaps-sdk/src/jsx/components/Image.ts b/packages/snaps-sdk/src/jsx/components/Image.ts
index e81821f225..ae4e2fcf36 100644
--- a/packages/snaps-sdk/src/jsx/components/Image.ts
+++ b/packages/snaps-sdk/src/jsx/components/Image.ts
@@ -1,4 +1,5 @@
import { createSnapComponent } from '../component';
+import type { BorderRadius } from './utils';
/**
* The props of the {@link Image} component.
@@ -12,7 +13,7 @@ import { createSnapComponent } from '../component';
type ImageProps = {
src: string;
alt?: string | undefined;
- borderRadius?: 'none' | 'medium' | 'full' | undefined;
+ borderRadius?: BorderRadius | undefined;
};
const TYPE = 'Image';
diff --git a/packages/snaps-sdk/src/jsx/components/Row.ts b/packages/snaps-sdk/src/jsx/components/Row.ts
index b64511150f..44dc409f24 100644
--- a/packages/snaps-sdk/src/jsx/components/Row.ts
+++ b/packages/snaps-sdk/src/jsx/components/Row.ts
@@ -2,6 +2,7 @@ import { createSnapComponent } from '../component';
import type { AddressElement } from './Address';
import type { ImageElement } from './Image';
import type { LinkElement } from './Link';
+import type { SkeletonElement } from './Skeleton';
import type { TextElement } from './Text';
import type { ValueElement } from './Value';
@@ -13,7 +14,8 @@ export type RowChildren =
| ImageElement
| TextElement
| ValueElement
- | LinkElement;
+ | LinkElement
+ | SkeletonElement;
/**
* The props of the {@link Row} component.
diff --git a/packages/snaps-sdk/src/jsx/components/Skeleton.test.tsx b/packages/snaps-sdk/src/jsx/components/Skeleton.test.tsx
new file mode 100644
index 0000000000..6869203534
--- /dev/null
+++ b/packages/snaps-sdk/src/jsx/components/Skeleton.test.tsx
@@ -0,0 +1,17 @@
+import { Skeleton } from './Skeleton';
+
+describe('Skeleton', () => {
+ it('renders a skeleton component', () => {
+ const result = ;
+
+ expect(result).toStrictEqual({
+ type: 'Skeleton',
+ key: null,
+ props: {
+ width: 320,
+ height: 32,
+ borderRadius: 'medium',
+ },
+ });
+ });
+});
diff --git a/packages/snaps-sdk/src/jsx/components/Skeleton.ts b/packages/snaps-sdk/src/jsx/components/Skeleton.ts
new file mode 100644
index 0000000000..17510f5f15
--- /dev/null
+++ b/packages/snaps-sdk/src/jsx/components/Skeleton.ts
@@ -0,0 +1,36 @@
+import { createSnapComponent } from '../component';
+import type { BorderRadius } from './utils';
+
+/**
+ * The props of the {@link Skeleton} component.
+ *
+ * @param width - Width of the Skeleton.
+ * @param width - Height of the Skeleton.
+ * @param borderRadius - Border radius of the Skeleton.
+ */
+export type SkeletonProps = {
+ width?: number | string | undefined;
+ height?: number | string | undefined;
+ borderRadius?: BorderRadius | undefined;
+};
+
+const TYPE = 'Skeleton';
+
+/**
+ * A Skeleton component, which is used to display skeleton of loading content.
+ *
+ * @param props - The props of the component.
+ * @param props.width - Width of the Skeleton.
+ * @param props.width - Height of the Skeleton.
+ * @param props.borderRadius - Border radius of the Skeleton.
+ * @example
+ *
+ */
+export const Skeleton = createSnapComponent(TYPE);
+
+/**
+ * A Skeleton element.
+ *
+ * @see Skeleton
+ */
+export type SkeletonElement = ReturnType;
diff --git a/packages/snaps-sdk/src/jsx/components/Text.ts b/packages/snaps-sdk/src/jsx/components/Text.ts
index 9994713b2c..b7a6b81d79 100644
--- a/packages/snaps-sdk/src/jsx/components/Text.ts
+++ b/packages/snaps-sdk/src/jsx/components/Text.ts
@@ -3,12 +3,17 @@ import { createSnapComponent } from '../component';
import type { StandardFormattingElement } from './formatting';
import type { IconElement } from './Icon';
import type { LinkElement } from './Link';
+import type { SkeletonElement } from './Skeleton';
/**
* The children of the {@link Text} component.
*/
export type TextChildren = SnapsChildren<
- string | StandardFormattingElement | LinkElement | IconElement
+ | string
+ | StandardFormattingElement
+ | LinkElement
+ | IconElement
+ | SkeletonElement
>;
/**
diff --git a/packages/snaps-sdk/src/jsx/components/index.ts b/packages/snaps-sdk/src/jsx/components/index.ts
index d26cbcd338..2f4d9521ba 100644
--- a/packages/snaps-sdk/src/jsx/components/index.ts
+++ b/packages/snaps-sdk/src/jsx/components/index.ts
@@ -15,6 +15,7 @@ import type { ImageElement } from './Image';
import type { LinkElement } from './Link';
import type { RowElement } from './Row';
import type { SectionElement } from './Section';
+import type { SkeletonElement } from './Skeleton';
import type { SpinnerElement } from './Spinner';
import type { TextElement } from './Text';
import type { TooltipElement } from './Tooltip';
@@ -41,6 +42,8 @@ export * from './Footer';
export * from './Container';
export * from './Section';
export * from './Banner';
+export * from './Skeleton';
+export * from './utils';
/**
* A built-in JSX element, which can be used in a Snap user interface.
@@ -66,4 +69,5 @@ export type JSXElement =
| SpinnerElement
| TextElement
| TooltipElement
- | BannerElement;
+ | BannerElement
+ | SkeletonElement;
diff --git a/packages/snaps-sdk/src/jsx/components/utils.ts b/packages/snaps-sdk/src/jsx/components/utils.ts
new file mode 100644
index 0000000000..66b35ba763
--- /dev/null
+++ b/packages/snaps-sdk/src/jsx/components/utils.ts
@@ -0,0 +1,4 @@
+/**
+ * Definition of border radius.
+ */
+export type BorderRadius = 'none' | 'medium' | 'full' | undefined;
diff --git a/packages/snaps-sdk/src/jsx/validation.test.tsx b/packages/snaps-sdk/src/jsx/validation.test.tsx
index c656ac598f..f7c38514aa 100644
--- a/packages/snaps-sdk/src/jsx/validation.test.tsx
+++ b/packages/snaps-sdk/src/jsx/validation.test.tsx
@@ -34,6 +34,7 @@ import {
Section,
Avatar,
Banner,
+ Skeleton,
} from './components';
import {
AddressStruct,
@@ -72,6 +73,7 @@ import {
SectionStruct,
AvatarStruct,
BannerStruct,
+ SkeletonStruct,
} from './validation';
describe('KeyStruct', () => {
@@ -1618,3 +1620,34 @@ describe('BannerStruct', () => {
expect(is(value, BannerStruct)).toBe(false);
});
});
+
+describe('SkeletonStruct', () => {
+ it.each([
+ ,
+ ,
+ ,
+ ,
+ ,
+ ,
+ ,
+ ])(`validates a Skeleton element`, (value) => {
+ expect(is(value, SkeletonStruct)).toBe(true);
+ });
+
+ it.each([
+ 'foo',
+ 42,
+ null,
+ undefined,
+ {},
+ [],
+ // @ts-expect-error - Invalid props.
+ foo,
+ // @ts-expect-error - Invalid props.
+ } severity="info">
+ foo
+ ,
+ ])('does not validate "%p"', (value) => {
+ expect(is(value, SkeletonStruct)).toBe(false);
+ });
+});
diff --git a/packages/snaps-sdk/src/jsx/validation.ts b/packages/snaps-sdk/src/jsx/validation.ts
index 5b6c03a5e1..6d286850ef 100644
--- a/packages/snaps-sdk/src/jsx/validation.ts
+++ b/packages/snaps-sdk/src/jsx/validation.ts
@@ -48,7 +48,7 @@ import type {
SnapsChildren,
StringElement,
} from './component';
-import type { AvatarElement } from './components';
+import type { AvatarElement, SkeletonElement } from './components';
import {
type AddressElement,
type BoldElement,
@@ -692,6 +692,17 @@ export const LinkStruct: Describe = element('Link', {
]),
});
+/**
+ * A struct for the {@link SkeletonElement} type.
+ */
+export const SkeletonStruct: Describe = element('Skeleton', {
+ width: optional(union([number(), string()])),
+ height: optional(union([number(), string()])),
+ borderRadius: optional(
+ nullUnion([literal('none'), literal('medium'), literal('full')]),
+ ),
+});
+
/**
* A struct for the {@link TextElement} type.
*/
@@ -701,7 +712,13 @@ export const TextStruct: Describe = element('Text', {
if (typeof value === 'string') {
return string();
}
- return typedUnion([BoldStruct, ItalicStruct, LinkStruct, IconStruct]);
+ return typedUnion([
+ BoldStruct,
+ ItalicStruct,
+ LinkStruct,
+ IconStruct,
+ SkeletonStruct,
+ ]);
}),
]),
alignment: optional(
@@ -797,6 +814,7 @@ export const BannerStruct: Describe = element('Banner', {
ButtonStruct,
BoldStruct,
ItalicStruct,
+ SkeletonStruct,
]),
title: string(),
severity: union([
@@ -818,6 +836,7 @@ export const RowStruct: Describe = element('Row', {
TextStruct,
ValueStruct,
LinkStruct,
+ SkeletonStruct,
]),
variant: optional(
nullUnion([literal('default'), literal('warning'), literal('critical')]),
@@ -863,6 +882,7 @@ export const BoxChildStruct = typedUnion([
SectionStruct,
AvatarStruct,
BannerStruct,
+ SkeletonStruct,
]);
/**
@@ -932,6 +952,7 @@ export const JSXElementStruct: Describe = typedUnion([
SectionStruct,
AvatarStruct,
BannerStruct,
+ SkeletonStruct,
]);
/**