diff --git a/packages/snaps-rpc-methods/src/permitted/createInterface.test.tsx b/packages/snaps-rpc-methods/src/permitted/createInterface.test.tsx
index adb621d501..bac78f688d 100644
--- a/packages/snaps-rpc-methods/src/permitted/createInterface.test.tsx
+++ b/packages/snaps-rpc-methods/src/permitted/createInterface.test.tsx
@@ -195,7 +195,7 @@ describe('snap_createInterface', () => {
error: {
code: -32602,
message:
- 'Invalid params: At path: ui -- Expected type to be one of: "AccountSelector", "Address", "AssetSelector", "AddressInput", "Bold", "Box", "Button", "Copyable", "DateTimePicker", "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.',
+ 'Invalid params: At path: ui -- Expected type to be one of: "AccountSelector", "Address", "AssetSelector", "AddressInput", "Bold", "Box", "Button", "Copyable", "DateTimePicker", "Divider", "Dropdown", "RadioGroup", "Field", "FileInput", "Form", "Heading", "Input", "Image", "Italic", "Link", "Row", "Spinner", "Text", "Tooltip", "Checkbox", "Card", "Icon", "Selector", "Section", "Avatar", "Banner", "Skeleton", "CollapsibleSection", "Container", but received: undefined.',
stack: expect.any(String),
},
id: 1,
diff --git a/packages/snaps-sdk/src/jsx/components/CollapsibleSection.test.tsx b/packages/snaps-sdk/src/jsx/components/CollapsibleSection.test.tsx
new file mode 100644
index 0000000000..11eadfee0b
--- /dev/null
+++ b/packages/snaps-sdk/src/jsx/components/CollapsibleSection.test.tsx
@@ -0,0 +1,129 @@
+import { Address } from './Address';
+import { CollapsibleSection } from './CollapsibleSection';
+import { Row } from './Row';
+import { Text } from './Text';
+
+describe('CollapsibleSection', () => {
+ it('renders a collapsible section', () => {
+ const result = (
+
+ Hello
+
+ );
+
+ expect(result).toStrictEqual({
+ type: 'CollapsibleSection',
+ key: null,
+ props: {
+ label: 'Details',
+ children: {
+ type: 'Text',
+ key: null,
+ props: {
+ children: 'Hello',
+ },
+ },
+ },
+ });
+ });
+
+ it('renders a collapsible section with multiple children', () => {
+ const result = (
+
+
+
+
+
+
+
+
+ );
+
+ expect(result).toStrictEqual({
+ type: 'CollapsibleSection',
+ key: null,
+ props: {
+ label: 'Transaction details',
+ children: [
+ {
+ type: 'Row',
+ key: null,
+ props: {
+ label: 'From',
+ children: {
+ type: 'Address',
+ key: null,
+ props: {
+ address: '0x1234567890123456789012345678901234567890',
+ },
+ },
+ },
+ },
+ {
+ type: 'Row',
+ key: null,
+ props: {
+ label: 'To',
+ tooltip: 'This address has been deemed dangerous.',
+ variant: 'warning',
+ children: {
+ type: 'Address',
+ key: null,
+ props: {
+ address: '0x0000000000000000000000000000000000000000',
+ },
+ },
+ },
+ },
+ ],
+ },
+ });
+ });
+
+ it('renders a collapsible section with props', () => {
+ const result = (
+
+ Hello
+ World
+
+ );
+
+ expect(result).toStrictEqual({
+ type: 'CollapsibleSection',
+ key: null,
+ props: {
+ label: 'Details',
+ direction: 'horizontal',
+ alignment: 'space-between',
+ isExpanded: true,
+ isLoading: false,
+ children: [
+ {
+ type: 'Text',
+ key: null,
+ props: {
+ children: 'Hello',
+ },
+ },
+ {
+ type: 'Text',
+ key: null,
+ props: {
+ children: 'World',
+ },
+ },
+ ],
+ },
+ });
+ });
+});
diff --git a/packages/snaps-sdk/src/jsx/components/CollapsibleSection.ts b/packages/snaps-sdk/src/jsx/components/CollapsibleSection.ts
new file mode 100644
index 0000000000..3419674507
--- /dev/null
+++ b/packages/snaps-sdk/src/jsx/components/CollapsibleSection.ts
@@ -0,0 +1,65 @@
+import type { GenericSnapElement, SnapsChildren } from '../component';
+import { createSnapComponent } from '../component';
+
+/**
+ * The props of the {@link CollapsibleSection} component.
+ *
+ * @property children - The children of the collapsible section.
+ * @property label - The label of the collapsible section.
+ * @property isLoading - Whether the section is still loading.
+ * @property isExpanded - Whether the section should start expanded.
+ * @property direction - The direction to stack the components within the section. Defaults to `vertical`.
+ * @property alignment - The alignment mode to use within the section. Defaults to `start`.
+ * @category Component Props
+ */
+export type CollapsibleSectionProps = {
+ // We can't use `JSXElement` because it causes a circular reference.
+ children: SnapsChildren;
+ label: string;
+ isLoading?: boolean;
+ isExpanded?: boolean;
+ direction?: 'vertical' | 'horizontal' | undefined;
+ alignment?:
+ | 'start'
+ | 'center'
+ | 'end'
+ | 'space-between'
+ | 'space-around'
+ | undefined;
+};
+
+const TYPE = 'CollapsibleSection';
+
+/**
+ * A collapsible section component, which is used to group multiple components
+ * together with a label. The section can be expanded or collapsed by the user.
+ *
+ * @param props - The props of the component.
+ * @param props.children - The children of the collapsible section.
+ * @param props.label - The label of the collapsible section.
+ * @param props.direction - The direction that the children are aligned.
+ * @param props.alignment - The alignment of the children (a justify-content value).
+ * @returns A collapsible section element.
+ * @example
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * @category Components
+ */
+export const CollapsibleSection = createSnapComponent<
+ CollapsibleSectionProps,
+ typeof TYPE
+>(TYPE);
+
+/**
+ * A collapsible section element.
+ *
+ * @see {@link CollapsibleSection}
+ * @category Elements
+ */
+export type CollapsibleSectionElement = ReturnType;
diff --git a/packages/snaps-sdk/src/jsx/components/Section.ts b/packages/snaps-sdk/src/jsx/components/Section.ts
index b21ae3819c..fa03e589ca 100644
--- a/packages/snaps-sdk/src/jsx/components/Section.ts
+++ b/packages/snaps-sdk/src/jsx/components/Section.ts
@@ -5,6 +5,8 @@ import { createSnapComponent } from '../component';
* The props of the {@link Section} component.
*
* @property children - The children of the section.
+ * @property direction - The direction to stack the components within the section. Defaults to `vertical`.
+ * @property alignment - The alignment mode to use within the section. Defaults to `start`.
* @category Component Props
*/
export type SectionProps = {
diff --git a/packages/snaps-sdk/src/jsx/components/index.ts b/packages/snaps-sdk/src/jsx/components/index.ts
index 23d336570e..bdd486b1b1 100644
--- a/packages/snaps-sdk/src/jsx/components/index.ts
+++ b/packages/snaps-sdk/src/jsx/components/index.ts
@@ -3,6 +3,7 @@ import type { AvatarElement } from './Avatar';
import type { BannerElement } from './Banner';
import type { BoxElement } from './Box';
import type { CardElement } from './Card';
+import type { CollapsibleSectionElement } from './CollapsibleSection';
import type { ContainerElement } from './Container';
import type { CopyableElement } from './Copyable';
import type { DividerElement } from './Divider';
@@ -27,6 +28,7 @@ export * from './Address';
export * from './Avatar';
export * from './Box';
export * from './Card';
+export * from './CollapsibleSection';
export * from './Copyable';
export * from './Divider';
export * from './Value';
@@ -66,6 +68,7 @@ export type JSXElement =
| LinkElement
| RowElement
| SectionElement
+ | CollapsibleSectionElement
| SpinnerElement
| TextElement
| TooltipElement
diff --git a/packages/snaps-sdk/src/jsx/validation.test.tsx b/packages/snaps-sdk/src/jsx/validation.test.tsx
index d1f1a171ff..0925497925 100644
--- a/packages/snaps-sdk/src/jsx/validation.test.tsx
+++ b/packages/snaps-sdk/src/jsx/validation.test.tsx
@@ -39,6 +39,7 @@ import {
AddressInput,
AccountSelector,
DateTimePicker,
+ CollapsibleSection,
} from './components';
import {
AddressStruct,
@@ -82,6 +83,7 @@ import {
AddressInputStruct,
AccountSelectorStruct,
DateTimePickerStruct,
+ CollapsibleSectionStruct,
} from './validation';
describe('KeyStruct', () => {
@@ -1666,6 +1668,77 @@ describe('SectionStruct', () => {
});
});
+describe('CollapsibleSectionStruct', () => {
+ it.each([
+
+
+ Hello world!
+
+ ,
+
+
+
+
+
+
+
+ ,
+
+ foo
+
+
+
+ ,
+
+ foo
+
+
+
+ ,
+ ])('validates a collapsible section element', (value) => {
+ expect(is(value, CollapsibleSectionStruct)).toBe(true);
+ });
+
+ it.each([
+ 'foo',
+ 42,
+ null,
+ undefined,
+ {},
+ [],
+ // @ts-expect-error - Invalid props.
+ ,
+ // @ts-expect-error - Invalid props.
+ ,
+ // @ts-expect-error - Invalid props.
+ ,
+ // @ts-expect-error - Missing label.
+
+ foo
+ ,
+ foo,
+
+ foo
+ ,
+ // @ts-expect-error - Invalid props.
+
+
+ Hello world!
+
+ ,
+ ])('does not validate "%p"', (value) => {
+ expect(is(value, CollapsibleSectionStruct)).toBe(false);
+ });
+});
+
describe('isJSXElement', () => {
it.each([
foo,
diff --git a/packages/snaps-sdk/src/jsx/validation.ts b/packages/snaps-sdk/src/jsx/validation.ts
index d7fcdfda40..6e1edfadbf 100644
--- a/packages/snaps-sdk/src/jsx/validation.ts
+++ b/packages/snaps-sdk/src/jsx/validation.ts
@@ -81,6 +81,7 @@ import type {
SelectorOptionElement,
BannerElement,
DateTimePickerElement,
+ CollapsibleSectionElement,
} from './components';
import { IconName } from './components';
import type { Describe } from '../internals';
@@ -768,6 +769,29 @@ export const SectionStruct: Describe = element('Section', {
),
});
+/**
+ * A struct for the {@link CollapsibleSectionElement} type.
+ */
+export const CollapsibleSectionStruct: Describe =
+ element('CollapsibleSection', {
+ children: BoxChildrenStruct,
+ label: string(),
+ isLoading: optional(boolean()),
+ isExpanded: optional(boolean()),
+ direction: optional(
+ nullUnion([literal('horizontal'), literal('vertical')]),
+ ),
+ alignment: optional(
+ nullUnion([
+ literal('start'),
+ literal('center'),
+ literal('end'),
+ literal('space-between'),
+ literal('space-around'),
+ ]),
+ ),
+ });
+
/**
* A subset of JSX elements that are allowed as children of the Footer component.
* This set should include a single button or a tuple of two buttons.
@@ -1020,6 +1044,7 @@ export const BoxChildStruct = typedUnion([
AvatarStruct,
BannerStruct,
SkeletonStruct,
+ CollapsibleSectionStruct,
]);
/**
@@ -1094,6 +1119,7 @@ export const JSXElementStruct: Describe = typedUnion([
AvatarStruct,
BannerStruct,
SkeletonStruct,
+ CollapsibleSectionStruct,
]);
/**