Skip to content

Instantly share code, notes, and snippets.

@Jamiewarb
Created March 24, 2025 12:08
Show Gist options
  • Save Jamiewarb/0401401b4150375eb8c494ddbd85bcd5 to your computer and use it in GitHub Desktop.
Save Jamiewarb/0401401b4150375eb8c494ddbd85bcd5 to your computer and use it in GitHub Desktop.
OuterBlockItemComponent.tsx
import type { ObjectItemProps, ObjectMember } from 'sanity';
import { Card, Stack } from '@sanity/ui';
import { ModularBlocksProvider } from '../context/ModularBlocksProvider';
import type {
ContentMember,
fieldToChildFieldsMap,
SanityObjectItemPropsChildren,
} from '../types';
import type { ComponentType } from 'react';
/**
* Default to showing the `content` field in the second layer for each block if it's defined.
*/
const defaultFieldToChildFields: fieldToChildFieldsMap = {
default: ['content'],
};
export function makeOuterBlockItemComponentFn(
fieldToChildFields: fieldToChildFieldsMap = defaultFieldToChildFields,
) {
const OuterBlockItemComponent: ComponentType<ObjectItemProps> = (props) => {
if (
!props.children ||
typeof props.children !== 'object' ||
!('props' in props.children)
) {
return props.renderDefault(props);
}
const {
type,
children,
hasInnerBlocks = false,
} = getChildrenPreview(props, props.children as SanityObjectItemPropsChildren);
return (
<Stack>
<Card>
<Card margin={2} style={{ marginBottom: type === 'preview' ? '-10px' : '0' }}>
<ModularBlocksProvider>{props.renderDefault(props)}</ModularBlocksProvider>
</Card>
{
<Card
paddingX={6}
paddingBottom={hasInnerBlocks ? 5 : 4}
borderBottom={hasInnerBlocks}
>
<ModularBlocksProvider overviewPreview={true}>{children}</ModularBlocksProvider>
</Card>
}
</Card>
</Stack>
);
};
return OuterBlockItemComponent;
interface PreviewResult {
type: 'preview' | 'custom';
children: any; // Use any here to avoid ReactNode compatibility issues
hasInnerBlocks: boolean;
childFields?: Array<string>;
}
function getChildrenPreview(
props: ObjectItemProps,
children: SanityObjectItemPropsChildren,
): PreviewResult {
const type = props.value._type ?? 'default';
const childFields =
type in fieldToChildFields ? fieldToChildFields[type] : fieldToChildFields.default;
const childrenProps = children.props;
if (typeof childFields === 'function') {
return {
type: 'custom',
children: childFields(childrenProps),
hasInnerBlocks: false,
};
}
const members = childrenProps.children?.props?.members;
const contentMember = members?.find(
(member: ObjectMember) => 'name' in member && childFields?.includes(member.name),
) as ContentMember | undefined;
const modifiedMembers = !contentMember
? []
: [
{
...contentMember,
field: {
...(contentMember.field || {}),
schemaType: {
...(contentMember.field?.schemaType || {}),
title: ' ', // Empty string to hide original field title
},
},
},
];
// Deep clone object so the regular modal view is unaffected
// while showing only the "inner" modular content, and removing its "title" via empty string
return {
type: 'preview',
childFields,
hasInnerBlocks: !!contentMember,
children: {
...children,
props: {
...childrenProps,
children: {
...((childrenProps.children ?? {}) as Record<string, any>),
props: {
...((childrenProps.children?.props ?? {}) as Record<string, any>),
members: modifiedMembers,
},
},
},
},
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment