From f584d23bd4118d2b06e7c0264e4bbed8b6cfb6df Mon Sep 17 00:00:00 2001 From: david0xd Date: Thu, 16 Jan 2025 20:21:37 +0100 Subject: [PATCH 1/7] Add Skeleton component --- packages/snaps-sdk/src/jsx/components/Row.ts | 4 +- .../src/jsx/components/Skeleton.test.tsx | 17 ++++++++ .../snaps-sdk/src/jsx/components/Skeleton.ts | 40 +++++++++++++++++++ .../snaps-sdk/src/jsx/components/index.ts | 5 ++- .../snaps-sdk/src/jsx/validation.test.tsx | 34 ++++++++++++++++ packages/snaps-sdk/src/jsx/validation.ts | 16 +++++++- 6 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 packages/snaps-sdk/src/jsx/components/Skeleton.test.tsx create mode 100644 packages/snaps-sdk/src/jsx/components/Skeleton.ts 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..d45e821851 --- /dev/null +++ b/packages/snaps-sdk/src/jsx/components/Skeleton.ts @@ -0,0 +1,40 @@ +import { createSnapComponent } from '../component'; + +/** + * Definition of Skeleton border radius. + */ +export type SkeletonBorderRadius = 'none' | 'medium' | 'full' | undefined; + +/** + * 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; + height: number | string; + borderRadius?: SkeletonBorderRadius | 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/index.ts b/packages/snaps-sdk/src/jsx/components/index.ts index d26cbcd338..132faee1f1 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,7 @@ export * from './Footer'; export * from './Container'; export * from './Section'; export * from './Banner'; +export * from './Skeleton'; /** * A built-in JSX element, which can be used in a Snap user interface. @@ -66,4 +68,5 @@ export type JSXElement = | SpinnerElement | TextElement | TooltipElement - | BannerElement; + | BannerElement + | SkeletonElement; diff --git a/packages/snaps-sdk/src/jsx/validation.test.tsx b/packages/snaps-sdk/src/jsx/validation.test.tsx index c656ac598f..ac927cb01b 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,35 @@ 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. + , + // @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..a6366da1c9 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, @@ -807,6 +807,17 @@ export const BannerStruct: Describe = element('Banner', { ]), }); +/** + * A struct for the {@link SkeletonElement} type. + */ +export const SkeletonStruct: Describe = element('Skeleton', { + width: union([number(), string()]), + height: union([number(), string()]), + borderRadius: optional( + nullUnion([literal('none'), literal('medium'), literal('full')]), + ), +}); + /** * A struct for the {@link RowElement} type. */ @@ -818,6 +829,7 @@ export const RowStruct: Describe = element('Row', { TextStruct, ValueStruct, LinkStruct, + SkeletonStruct, ]), variant: optional( nullUnion([literal('default'), literal('warning'), literal('critical')]), @@ -863,6 +875,7 @@ export const BoxChildStruct = typedUnion([ SectionStruct, AvatarStruct, BannerStruct, + SkeletonStruct, ]); /** @@ -932,6 +945,7 @@ export const JSXElementStruct: Describe = typedUnion([ SectionStruct, AvatarStruct, BannerStruct, + SkeletonStruct, ]); /** From 134c92cc9f2d5d0e398e16b484e11498d4dff163 Mon Sep 17 00:00:00 2001 From: david0xd Date: Fri, 17 Jan 2025 10:17:35 +0100 Subject: [PATCH 2/7] Update unit test and manifest files --- packages/examples/packages/browserify-plugin/snap.manifest.json | 2 +- packages/examples/packages/browserify/snap.manifest.json | 2 +- .../snaps-rpc-methods/src/permitted/createInterface.test.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/examples/packages/browserify-plugin/snap.manifest.json b/packages/examples/packages/browserify-plugin/snap.manifest.json index 0d829905dc..350ba5cec7 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": "lkSFHl7pMpzkhmdLG9/SqFOP5DEYTyL6ZKTexdYomxo=", "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..6aa1257164 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": "iDg8E7RWL/Gi4GhXaKPJ00T60WATvJ1kKLffomUcsn0=", "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, From 20007bb72f8763de3219635311d05fb1cbb07fa1 Mon Sep 17 00:00:00 2001 From: david0xd Date: Fri, 17 Jan 2025 11:12:27 +0100 Subject: [PATCH 3/7] Refactor some code --- packages/snaps-sdk/src/jsx/components/Skeleton.ts | 10 +++------- packages/snaps-sdk/src/jsx/components/utils.ts | 4 ++++ 2 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 packages/snaps-sdk/src/jsx/components/utils.ts diff --git a/packages/snaps-sdk/src/jsx/components/Skeleton.ts b/packages/snaps-sdk/src/jsx/components/Skeleton.ts index d45e821851..c16d015e7b 100644 --- a/packages/snaps-sdk/src/jsx/components/Skeleton.ts +++ b/packages/snaps-sdk/src/jsx/components/Skeleton.ts @@ -1,9 +1,5 @@ import { createSnapComponent } from '../component'; - -/** - * Definition of Skeleton border radius. - */ -export type SkeletonBorderRadius = 'none' | 'medium' | 'full' | undefined; +import type { BorderRadius } from './utils'; /** * The props of the {@link Skeleton} component. @@ -15,7 +11,7 @@ export type SkeletonBorderRadius = 'none' | 'medium' | 'full' | undefined; export type SkeletonProps = { width: number | string; height: number | string; - borderRadius?: SkeletonBorderRadius | undefined; + borderRadius?: BorderRadius | undefined; }; const TYPE = 'Skeleton'; @@ -28,7 +24,7 @@ const TYPE = 'Skeleton'; * @param props.width - Height of the Skeleton. * @param props.borderRadius - Border radius of the Skeleton. * @example - * + * */ export const Skeleton = createSnapComponent(TYPE); 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; From 7135e1b295193a05656907eb37dedcad49800026 Mon Sep 17 00:00:00 2001 From: david0xd Date: Fri, 17 Jan 2025 11:57:25 +0100 Subject: [PATCH 4/7] Update BorderRadius type on Image component for consistency --- packages/snaps-sdk/src/jsx/components/Image.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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'; From e5ea7b65d777847f7ea1a7d904a450dc48bab371 Mon Sep 17 00:00:00 2001 From: david0xd Date: Fri, 17 Jan 2025 12:03:54 +0100 Subject: [PATCH 5/7] Make some updates to extend Skeleton params possibilities --- .../snaps-sdk/src/jsx/components/Banner.ts | 2 ++ .../snaps-sdk/src/jsx/components/Skeleton.ts | 4 +-- packages/snaps-sdk/src/jsx/components/Text.ts | 7 ++++- .../snaps-sdk/src/jsx/validation.test.tsx | 3 +- packages/snaps-sdk/src/jsx/validation.ts | 31 ++++++++++++------- 5 files changed, 30 insertions(+), 17 deletions(-) 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/Skeleton.ts b/packages/snaps-sdk/src/jsx/components/Skeleton.ts index c16d015e7b..17510f5f15 100644 --- a/packages/snaps-sdk/src/jsx/components/Skeleton.ts +++ b/packages/snaps-sdk/src/jsx/components/Skeleton.ts @@ -9,8 +9,8 @@ import type { BorderRadius } from './utils'; * @param borderRadius - Border radius of the Skeleton. */ export type SkeletonProps = { - width: number | string; - height: number | string; + width?: number | string | undefined; + height?: number | string | undefined; borderRadius?: BorderRadius | undefined; }; 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/validation.test.tsx b/packages/snaps-sdk/src/jsx/validation.test.tsx index ac927cb01b..f7c38514aa 100644 --- a/packages/snaps-sdk/src/jsx/validation.test.tsx +++ b/packages/snaps-sdk/src/jsx/validation.test.tsx @@ -1623,6 +1623,7 @@ describe('BannerStruct', () => { describe('SkeletonStruct', () => { it.each([ + , , , , @@ -1641,8 +1642,6 @@ describe('SkeletonStruct', () => { {}, [], // @ts-expect-error - Invalid props. - , - // @ts-expect-error - Invalid props. foo, // @ts-expect-error - Invalid props. } severity="info"> diff --git a/packages/snaps-sdk/src/jsx/validation.ts b/packages/snaps-sdk/src/jsx/validation.ts index a6366da1c9..6d286850ef 100644 --- a/packages/snaps-sdk/src/jsx/validation.ts +++ b/packages/snaps-sdk/src/jsx/validation.ts @@ -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([ @@ -807,17 +825,6 @@ export const BannerStruct: Describe = element('Banner', { ]), }); -/** - * A struct for the {@link SkeletonElement} type. - */ -export const SkeletonStruct: Describe = element('Skeleton', { - width: union([number(), string()]), - height: union([number(), string()]), - borderRadius: optional( - nullUnion([literal('none'), literal('medium'), literal('full')]), - ), -}); - /** * A struct for the {@link RowElement} type. */ From fb7ff053fc33c1d280f846ac2de6f97510c790b2 Mon Sep 17 00:00:00 2001 From: david0xd Date: Fri, 17 Jan 2025 12:37:20 +0100 Subject: [PATCH 6/7] Update export and unit tests --- packages/snaps-sdk/src/internals/structs.test.ts | 4 ++-- packages/snaps-sdk/src/jsx/components/index.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) 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/index.ts b/packages/snaps-sdk/src/jsx/components/index.ts index 132faee1f1..2f4d9521ba 100644 --- a/packages/snaps-sdk/src/jsx/components/index.ts +++ b/packages/snaps-sdk/src/jsx/components/index.ts @@ -43,6 +43,7 @@ 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. From 91848fdcab67f40211554628889bb7cf5e85c2b3 Mon Sep 17 00:00:00 2001 From: david0xd Date: Fri, 17 Jan 2025 12:39:34 +0100 Subject: [PATCH 7/7] Update manifests --- packages/examples/packages/browserify-plugin/snap.manifest.json | 2 +- packages/examples/packages/browserify/snap.manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/examples/packages/browserify-plugin/snap.manifest.json b/packages/examples/packages/browserify-plugin/snap.manifest.json index 350ba5cec7..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": "lkSFHl7pMpzkhmdLG9/SqFOP5DEYTyL6ZKTexdYomxo=", + "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 6aa1257164..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": "iDg8E7RWL/Gi4GhXaKPJ00T60WATvJ1kKLffomUcsn0=", + "shasum": "9K88gT7CbGCXTc/Qx63zbS91VQtHTkFFXWLjyXoM6YU=", "location": { "npm": { "filePath": "dist/bundle.js",