Created
March 24, 2025 12:08
-
-
Save Jamiewarb/0401401b4150375eb8c494ddbd85bcd5 to your computer and use it in GitHub Desktop.
OuterBlockItemComponent.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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