Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Skeleton component #3024

Merged
merged 8 commits into from
Jan 20, 2025
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add Skeleton component
  • Loading branch information
david0xd committed Jan 20, 2025
commit 61691131bf103f37fb131a4322f1c4d6a42ed0bf
4 changes: 3 additions & 1 deletion packages/snaps-sdk/src/jsx/components/Row.ts
Original file line number Diff line number Diff line change
@@ -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.
17 changes: 17 additions & 0 deletions packages/snaps-sdk/src/jsx/components/Skeleton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Skeleton } from './Skeleton';

describe('Skeleton', () => {
it('renders a skeleton component', () => {
const result = <Skeleton width={320} height={32} borderRadius="medium" />;

expect(result).toStrictEqual({
type: 'Skeleton',
key: null,
props: {
width: 320,
height: 32,
borderRadius: 'medium',
},
});
});
});
40 changes: 40 additions & 0 deletions packages/snaps-sdk/src/jsx/components/Skeleton.ts
Original file line number Diff line number Diff line change
@@ -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
* <Skeleton height={32} width={50%} />
*/
export const Skeleton = createSnapComponent<SkeletonProps, typeof TYPE>(TYPE);

/**
* A Skeleton element.
*
* @see Skeleton
*/
export type SkeletonElement = ReturnType<typeof Skeleton>;
5 changes: 4 additions & 1 deletion packages/snaps-sdk/src/jsx/components/index.ts
Original file line number Diff line number Diff line change
@@ -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;
34 changes: 34 additions & 0 deletions packages/snaps-sdk/src/jsx/validation.test.tsx
Original file line number Diff line number Diff line change
@@ -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([
<Skeleton width={320} height={32} />,
<Skeleton width="30%" height="30%" />,
<Skeleton width={32} height="30%" />,
<Skeleton width="30%" height={32} />,
<Skeleton width="30%" height={32} borderRadius="full" />,
<Skeleton width={32} height="30%" borderRadius="medium" />,
])(`validates a Skeleton element`, (value) => {
expect(is(value, SkeletonStruct)).toBe(true);
});

it.each([
'foo',
42,
null,
undefined,
{},
[],
// @ts-expect-error - Invalid props.
<Skeleton />,
// @ts-expect-error - Invalid props.
<Skeleton foo="bar">foo</Skeleton>,
// @ts-expect-error - Invalid props.
<Skeleton title={<Copyable value="bar" />} severity="info">
<Text>foo</Text>
</Skeleton>,
])('does not validate "%p"', (value) => {
expect(is(value, SkeletonStruct)).toBe(false);
});
});
16 changes: 15 additions & 1 deletion packages/snaps-sdk/src/jsx/validation.ts
Original file line number Diff line number Diff line change
@@ -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<BannerElement> = element('Banner', {
]),
});

/**
* A struct for the {@link SkeletonElement} type.
*/
export const SkeletonStruct: Describe<SkeletonElement> = 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<RowElement> = 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<JSXElement> = typedUnion([
SectionStruct,
AvatarStruct,
BannerStruct,
SkeletonStruct,
]);

/**