UNPKG

sanity

Version:

Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches

85 lines (72 loc) 3.38 kB
/* eslint-disable @typescript-eslint/ban-types */ import {type ComponentType, createElement, Fragment, useMemo} from 'react' import {flattenConfig} from '../../config' import {useSource} from '../../studio' import {type PluginOptions} from '../types' const emptyRender = () => createElement(Fragment) function _createMiddlewareComponent<T extends {}>( defaultComponent: ComponentType<T>, middlewareComponents: ComponentType<T>[], ): ComponentType<T> { return (outerProps: T) => { // This is the inner "layer" of the middleware chain // Here we render the _default_ component (typically Sanity's component) let next = (props: T) => createElement(defaultComponent, props) for (const middleware of middlewareComponents) { // As we progress through the chain, the meaning of "renderDefault" changes. // At a given layer in the chain, the _next_ layer is the "default". const renderDefault = next // Here we replace `next` so that the _previous_ layer may use this as its _next_. next = (props) => createElement(middleware, {...props, renderDefault}) } return next({ ...outerProps, // NOTE: it's safe to pass the empty render function, since it'll be overwritten in the next step (above). // NOTE: it's important that the default component does not use `renderDefault`, since it will // get the `emptyRender` callback will be passed when the middleware stack is empty. renderDefault: emptyRender, }) } } /** * @internal * This hook returns a component based on the Components API middleware chain. * * - The `pick` function is used to select a component from the provided plugin options in the configuration. * - The `defaultComponent` is the default component that gets rendered with `renderDefault`. * The `renderDefault` function is added to the props of the middleware components so that they can render the default * component and continue the middleware chain. * * @example * Example usage of: * * ```ts * const StudioLayout = useMiddlewareComponents({ * pick: (plugin) => plugin.studio?.components?.layout, * defaultComponent: StudioLayout, * }) * * return <StudioLayout /> *``` */ export function useMiddlewareComponents<T extends {}>(props: { pick: (plugin: PluginOptions) => ComponentType<T> defaultComponent: ComponentType<T> }): ComponentType<T> { const {options} = useSource().__internal const {defaultComponent, pick} = props return useMemo(() => { // Flatten the config tree into a list of configs const flattened = [...flattenConfig(options, [])] // Since the middleware chain is executed backwards, we need to reverse the list of configs here. // This is important because we want the order of the Components API to be consistent with the order of the other APIs. flattened.reverse() // Pick the middleware components from the configs const pickedComponents = flattened.map(({config}) => pick(config)) // Since we try to pick the components in all configs, some results may be undefined. // Therefore, we filter these values out before passing the result to the middleware creator. const result = pickedComponents.filter(Boolean) // Create the middleware component return _createMiddlewareComponent(defaultComponent, result) }, [defaultComponent, options, pick]) }