diff --git a/packages/core/components/ComponentList/index.tsx b/packages/core/components/ComponentList/index.tsx index e1fd9de623..45c13fa61d 100644 --- a/packages/core/components/ComponentList/index.tsx +++ b/packages/core/components/ComponentList/index.tsx @@ -3,45 +3,88 @@ import styles from "./styles.module.css"; import getClassNameFactory from "../../lib/get-class-name-factory"; import { Draggable } from "../Draggable"; import { DragIcon } from "../DragIcon"; -import { ReactNode } from "react"; +import { ReactNode, useMemo } from "react"; import { useAppContext } from "../Puck/context"; import { ChevronDown, ChevronUp } from "react-feather"; const getClassName = getClassNameFactory("ComponentList", styles); const getClassNameItem = getClassNameFactory("ComponentListItem", styles); -const ComponentListItem = ({ +export const ComponentListDraggable = ({ + children, + id, + index, +}: { + children: ReactNode; + id: string; + index: number; +}) => ( + + {() => children} + +); + +export const ComponentListItem = ({ component, index, - id, }: { component: string; index: number; - id: string; }) => { + const { customUi } = useAppContext(); + + const CustomComponentListItem = useMemo( + () => customUi.componentListItem || "div", + [customUi] + ); + return (
- getClassNameItem("draggable")} - > - {() => ( - <> -
{component}
-
- + + +
+
+
{component}
+
+ +
- - )} - +
+
+
); }; +export const ComponentListDroppable = ({ + children, + droppableId = "component-list", + direction = "vertical", +}: { + children: ReactNode; + droppableId?: string; + direction?: "vertical" | "horizontal"; +}) => { + return ( + + {(provided, snapshot) => ( +
+ {children} + + {/* Use different element so we don't clash with :last-of-type */} + {provided.placeholder} +
+ )} +
+ ); +}; + const ComponentList = ({ children, title, @@ -84,34 +127,20 @@ const ComponentList = ({
)}
- - {(provided, snapshot) => ( -
- {children || - Object.keys(config.components).map((componentKey, i) => { - return ( - - ); - })} - {/* Use different element so we don't clash with :last-of-type */} - {provided.placeholder} -
- )} -
+ {children || + Object.keys(config.components).map((componentKey, i) => { + return ( + + ); + })} +
); diff --git a/packages/core/components/ComponentList/styles.module.css b/packages/core/components/ComponentList/styles.module.css index b3760fef22..8089d21a0c 100644 --- a/packages/core/components/ComponentList/styles.module.css +++ b/packages/core/components/ComponentList/styles.module.css @@ -40,8 +40,12 @@ margin-left: auto; } -.ComponentListItem:last-of-type .ComponentListItem-draggable { - margin-bottom: 0px; +.ComponentListItem:last-of-type .ComponentListItem-draggableWrapper { + padding-bottom: 0px; +} + +.ComponentListItem-draggableWrapper { + padding-bottom: 12px; } .ComponentListItem-draggable { @@ -54,7 +58,6 @@ justify-content: space-between; align-items: center; cursor: grab; - margin-bottom: 12px; } .ComponentListItem-name { diff --git a/packages/core/components/InputOrGroup/index.tsx b/packages/core/components/InputOrGroup/index.tsx index 57bf90e891..382d85e7e9 100644 --- a/packages/core/components/InputOrGroup/index.tsx +++ b/packages/core/components/InputOrGroup/index.tsx @@ -14,6 +14,7 @@ import { import { Lock } from "react-feather"; import { useDebouncedCallback } from "use-debounce"; import { ObjectField } from "./fields/ObjectField"; +import { useAppContext } from "../Puck/context"; const getClassName = getClassNameFactory("Input", styles); @@ -76,9 +77,9 @@ export const FieldLabelInternal = ({ ); }; -export type InputProps = { +export type InputProps> = { name: string; - field: Field; + field: F; value: any; id: string; label?: string; @@ -88,6 +89,8 @@ export type InputProps = { }; export const InputOrGroup = ({ onChange, ...props }: InputProps) => { + const { customUi } = useAppContext(); + const { name, field, value, readOnly } = props; const [localValue, setLocalValue] = useState(value); @@ -114,30 +117,6 @@ export const InputOrGroup = ({ onChange, ...props }: InputProps) => { onChange: onChangeLocal, }; - if (field.type === "array") { - return ; - } - - if (field.type === "external") { - return ; - } - - if (field.type === "object") { - return ; - } - - if (field.type === "select") { - return ; - } - - if (field.type === "textarea") { - return ; - } - - if (field.type === "radio") { - return ; - } - if (field.type === "custom") { if (!field.render) { return null; @@ -155,5 +134,19 @@ export const InputOrGroup = ({ onChange, ...props }: InputProps) => { ); } - return ; + const render = { + ...customUi.fields, + array: customUi.fields?.array || ArrayField, + external: customUi.fields?.external || ExternalField, + object: customUi.fields?.object || ObjectField, + select: customUi.fields?.select || SelectField, + textarea: customUi.fields?.textarea || TextareaField, + radio: customUi.fields?.radio || RadioField, + text: customUi.fields?.text || DefaultField, + number: customUi.fields?.number || DefaultField, + }; + + const Render = render[field.type] as (props: InputProps) => ReactNode; + + return ; }; diff --git a/packages/core/components/Puck/components/Components/index.tsx b/packages/core/components/Puck/components/Components/index.tsx index 85a8e244ff..81e9096925 100644 --- a/packages/core/components/Puck/components/Components/index.tsx +++ b/packages/core/components/Puck/components/Components/index.tsx @@ -1,13 +1,18 @@ import { useComponentList } from "../../../../lib/use-component-list"; import { useAppContext } from "../../context"; import { ComponentList } from "../../../ComponentList"; +import { useMemo } from "react"; export const Components = () => { - const { config, state } = useAppContext(); + const { config, state, customUi } = useAppContext(); const componentList = useComponentList(config, state.ui); + const Wrapper = useMemo(() => customUi.componentList || "div", [customUi]); + return ( -
{componentList ? componentList : }
+ + {componentList ? componentList : } + ); }; diff --git a/packages/core/components/Puck/components/Fields/index.tsx b/packages/core/components/Puck/components/Fields/index.tsx index 1fb4d04dc0..8965402854 100644 --- a/packages/core/components/Puck/components/Fields/index.tsx +++ b/packages/core/components/Puck/components/Fields/index.tsx @@ -12,6 +12,7 @@ import { useAppContext } from "../../context"; import styles from "./styles.module.css"; import { getClassNameFactory } from "../../../../lib"; +import { ReactNode, useMemo } from "react"; const getClassName = getClassNameFactory("PuckFields", styles); @@ -19,9 +20,35 @@ const defaultPageFields: Record = { title: { type: "text" }, }; +const DefaultForm = ({ + children, + isLoading, +}: { + children: ReactNode; + isLoading: boolean; +}) => { + return ( +
+ {children} + {isLoading && ( +
+ +
+ )} +
+ ); +}; + export const Fields = () => { - const { selectedItem, state, dispatch, config, resolveData, componentState } = - useAppContext(); + const { + selectedItem, + state, + dispatch, + config, + resolveData, + componentState, + customUi, + } = useAppContext(); const { data, ui } = state; const { itemSelector } = ui; @@ -41,6 +68,13 @@ export const Fields = () => { // DEPRECATED const rootProps = data.root.props || data.root; + const Wrapper = useMemo( + () => + (itemSelector ? customUi.form : customUi.rootForm || customUi.form) || + DefaultForm, + [customUi, itemSelector] + ); + return (
{ e.preventDefault(); }} > -
+ {Object.keys(fields).map((fieldName) => { const field = fields[fieldName]; @@ -162,12 +196,7 @@ export const Fields = () => { ); } })} -
- {isLoading && ( -
- -
- )} +
); }; diff --git a/packages/core/components/Puck/components/Outline/index.tsx b/packages/core/components/Puck/components/Outline/index.tsx index 12eda27642..89f240f4dd 100644 --- a/packages/core/components/Puck/components/Outline/index.tsx +++ b/packages/core/components/Puck/components/Outline/index.tsx @@ -4,11 +4,11 @@ import { rootDroppableId } from "../../../../lib/root-droppable-id"; import { LayerTree } from "../../../LayerTree"; import { useAppContext } from "../../context"; import { dropZoneContext } from "../../../DropZone"; -import { useCallback } from "react"; +import { useCallback, useMemo } from "react"; import { ItemSelector } from "../../../../lib/get-item"; export const Outline = () => { - const { dispatch, state } = useAppContext(); + const { dispatch, state, customUi } = useAppContext(); const { data, ui } = state; const { itemSelector } = ui; @@ -22,8 +22,10 @@ export const Outline = () => { [] ); + const Wrapper = useMemo(() => customUi.outline || "div", [customUi]); + return ( -
+ {(ctx) => ( <> @@ -54,6 +56,6 @@ export const Outline = () => { )} -
+ ); }; diff --git a/packages/core/components/Puck/context.tsx b/packages/core/components/Puck/context.tsx index 835e83c6d5..91540fb9fa 100644 --- a/packages/core/components/Puck/context.tsx +++ b/packages/core/components/Puck/context.tsx @@ -3,6 +3,7 @@ import { AppState, Config, UiState } from "../../types/Config"; import { PuckAction } from "../../reducer"; import { getItem } from "../../lib/get-item"; import { Plugin } from "../../types/Plugin"; +import { CustomUi } from "../../types/CustomUi"; export const defaultAppState: AppState = { data: { content: [], root: { props: { title: "" } } }, @@ -22,6 +23,7 @@ type AppContext = { componentState: Record; resolveData: (newAppState: AppState) => void; plugins: Plugin[]; + customUi: Partial; }; export const appContext = createContext({ @@ -31,6 +33,7 @@ export const appContext = createContext({ componentState: {}, resolveData: () => {}, plugins: [], + customUi: {}, }); export const AppProvider = appContext.Provider; diff --git a/packages/core/components/Puck/index.tsx b/packages/core/components/Puck/index.tsx index 8e928ab4ab..ae3da1d0d8 100644 --- a/packages/core/components/Puck/index.tsx +++ b/packages/core/components/Puck/index.tsx @@ -3,6 +3,7 @@ import { ReactNode, useCallback, useEffect, + useMemo, useReducer, useState, } from "react"; @@ -31,39 +32,10 @@ import { Fields } from "./components/Fields"; import { Components } from "./components/Components"; import { Preview } from "./components/Preview"; import { Outline } from "./components/Outline"; +import { CustomUi } from "../../types/CustomUi"; const getClassName = getClassNameFactory("Puck", styles); -export const PluginRenderer = ({ - children, - dispatch, - state, - plugins, - renderMethod, -}: { - children: ReactNode; - dispatch: (action: PuckAction) => void; - state: AppState; - plugins; - renderMethod: - | "renderRoot" - | "renderRootFields" - | "renderFields" - | "renderComponentList"; -}) => { - return plugins - .filter((item) => item[renderMethod]) - .map((item) => item[renderMethod]) - .reduce( - (accChildren, Item) => ( - - {accChildren} - - ), - children - ); -}; - export function Puck({ children, config, @@ -72,6 +44,7 @@ export function Puck({ onChange, onPublish, plugins = [], + customUi = {}, renderComponentList, renderHeader, renderHeaderActions, @@ -85,6 +58,7 @@ export function Puck({ onChange?: (data: Data) => void; onPublish: (data: Data) => void; plugins?: Plugin[]; + customUi?: Partial; renderComponentList?: (props: { children: ReactNode; dispatch: (action: PuckAction) => void; @@ -159,58 +133,6 @@ export function Puck({ const selectedItem = itemSelector ? getItem(itemSelector, data) : null; - const PageFieldWrapper = useCallback( - (props) => ( - - {props.children} - - ), - [] - ); - - const ComponentFieldWrapper = useCallback( - (props) => ( - - {props.children} - - ), - [] - ); - - const ComponentListWrapper = useCallback((props) => { - const children = ( - - {props.children} - - ); - - // User's render method wraps the plugin render methods - return renderComponentList - ? renderComponentList({ - children, - dispatch, - state: appState, - }) - : children; - }, []); - - const FieldWrapper = itemSelector ? ComponentFieldWrapper : PageFieldWrapper; - useEffect(() => { if (onChange) onChange(data); }, [data]); @@ -284,6 +206,85 @@ export function Puck({ const disableZoom = children ? true : false; + const defaultRender = ({ children }) => children; + const defaultRenderNoChildren = () => <>; + + // DEPRECATED + const defaultHeaderRender = useMemo(() => { + if (renderHeader) { + console.warn( + "`renderHeader` is deprecated. Please use `customUi.header` and the `usePuck` hook instead" + ); + + const RenderHeader = ({ actions, ...props }) => { + const Comp = renderHeader!; + + return ( + + {actions} + + ); + }; + + return RenderHeader; + } + + return defaultRender; + }, [renderHeader]); + + // DEPRECATED + const defaultHeaderActionsRender = useMemo(() => { + if (renderHeaderActions) { + console.warn( + "`renderHeaderActions` is deprecated. Please use `customUi.headerActions` and the `usePuck` hook instead." + ); + + const RenderHeader = (props) => { + const Comp = renderHeaderActions!; + + return ; + }; + + return RenderHeader; + } + + return defaultRenderNoChildren; + }, [renderHeader]); + + // Load all plugins into the custom ui + const loadedCustomUi = useMemo(() => { + const collected: Partial = customUi; + + plugins.forEach((plugin) => { + Object.keys(plugin.customUi).forEach((customUiType) => { + const childNode = collected[customUiType]; + + const Comp = (props) => + plugin.customUi[customUiType]({ + ...props, + children: childNode ? childNode(props) : props.children, + }); + + collected[customUiType] = Comp; + }); + }); + + return collected; + }, [plugins]); + + const CustomPuck = useMemo( + () => loadedCustomUi.puck || defaultRender, + [loadedCustomUi] + ); + const CustomHeader = useMemo( + () => loadedCustomUi.header || defaultHeaderRender, + [loadedCustomUi] + ); + const CustomHeaderActions = useMemo( + () => loadedCustomUi.headerActions || defaultHeaderActionsRender, + [loadedCustomUi] + ); + return (
- {children || ( -
-
- {renderHeader ? ( - renderHeader({ - children: ( + + {children || ( +
+ + - ), - dispatch, - state: appState, - }) - ) : ( -
-
-
- { - toggleSidebars("left"); - }} - title="Toggle left sidebar" - > - - + + } + > +
+
+
+
+ { + toggleSidebars("left"); + }} + title="Toggle left sidebar" + > + + +
+
+ { + toggleSidebars("right"); + }} + title="Toggle right sidebar" + > + + +
+<<<<<<< HEAD
{ @@ -448,59 +462,86 @@ export function Puck({ ) : ( +======= +
+ + {headerTitle || data.root.props.title || "Page"} + {headerPath && ( + <> + {" "} + + {headerPath} + + +>>>>>>> b9c0ed2 (feat: introduce UI overrides API) )} - + +
+
+
+ { + return setMenuOpen(!menuOpen); + }} + title="Toggle menu bar" + > + {menuOpen ? ( + + ) : ( + + )} + +
+ } + setMenuOpen={setMenuOpen} + />
-
-
- )} -
-
- - - - - - -
-
setItemSelector(null)} - > -
- +
+ +
+ + + + + +
- {/* Fill empty space under root */}
-
-
- + className={getClassName("frame")} + onClick={() => setItemSelector(null)} + > +
+ +
+ {/* Fill empty space under root */} +
+
+
- +
- - )} + )} + diff --git a/packages/core/components/SidebarSection/index.tsx b/packages/core/components/SidebarSection/index.tsx index 47ead5dc4f..b6d4bc741a 100644 --- a/packages/core/components/SidebarSection/index.tsx +++ b/packages/core/components/SidebarSection/index.tsx @@ -14,6 +14,7 @@ export const SidebarSection = ({ title, background, showBreadcrumbs, + noBorderTop, noPadding, isLoading, }: { @@ -21,6 +22,7 @@ export const SidebarSection = ({ title: ReactNode; background?: string; showBreadcrumbs?: boolean; + noBorderTop?: boolean; noPadding?: boolean; isLoading?: boolean | null; }) => { @@ -28,7 +30,10 @@ export const SidebarSection = ({ const breadcrumbs = useBreadcrumbs(1); return ( -
+
{showBreadcrumbs diff --git a/packages/core/components/SidebarSection/styles.module.css b/packages/core/components/SidebarSection/styles.module.css index f114e6d062..e323b5b200 100644 --- a/packages/core/components/SidebarSection/styles.module.css +++ b/packages/core/components/SidebarSection/styles.module.css @@ -13,19 +13,23 @@ background: white; padding: 16px; border-bottom: 1px solid var(--puck-color-grey-8); + border-top: 1px solid var(--puck-color-grey-8); overflow-x: auto; } +.SidebarSection--noBorderTop > .SidebarSection-title { + border-top: 0px; +} + .SidebarSection-content { - border-bottom: 1px solid var(--puck-color-grey-8); padding: 16px; } -.SidebarSection--noPadding .SidebarSection-content { +.SidebarSection--noPadding > .SidebarSection-content { padding: 0px; } -.SidebarSection--noPadding .SidebarSection-content:last-child { +.SidebarSection--noPadding > .SidebarSection-content:last-child { padding-bottom: 4px; } diff --git a/packages/core/index.ts b/packages/core/index.ts index 0a0b4d3ed9..03a8b59c15 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -4,6 +4,10 @@ export type { PuckAction } from "./reducer/actions"; export * from "./types/Config"; export * from "./components/Button"; +export { + ComponentListDraggable, + ComponentListDroppable, +} from "./components/ComponentList"; // DEPRECATED export * from "./components/DropZone"; export * from "./components/IconButton"; diff --git a/packages/core/types/Config.tsx b/packages/core/types/Config.tsx index 293f472482..b5407408fa 100644 --- a/packages/core/types/Config.tsx +++ b/packages/core/types/Config.tsx @@ -12,11 +12,26 @@ export type BaseField = { }; export type TextField = BaseField & { - type: "text" | "number" | "textarea"; + type: "text"; +}; +export type NumberField = BaseField & { + type: "number"; +}; + +export type TextareaField = BaseField & { + type: "textarea"; }; export type SelectField = BaseField & { - type: "select" | "radio"; + type: "select"; + options: { + label: string; + value: string | number | boolean; + }[]; +}; + +export type RadioField = BaseField & { + type: "radio"; options: { label: string; value: string | number | boolean; @@ -94,7 +109,10 @@ export type Field< Props extends { [key: string]: any } = { [key: string]: any } > = | TextField + | NumberField + | TextareaField | SelectField + | RadioField | ArrayField | ObjectField | ExternalField diff --git a/packages/core/types/CustomUi.ts b/packages/core/types/CustomUi.ts new file mode 100644 index 0000000000..ac519ddb3a --- /dev/null +++ b/packages/core/types/CustomUi.ts @@ -0,0 +1,47 @@ +import { ReactElement, ReactNode } from "react"; +import { InputProps } from "../components/InputOrGroup"; +import { Field } from "./Config"; + +// Plugins can use `usePuck` instead of relying on props +type RenderFunc< + Props extends { [key: string]: any } = { children: ReactNode } +> = (props: Props) => ReactElement; + +// All direct render methods, excluding fields +export const customUiKeys = [ + "header", + "headerActions", + "root", + "rootForm", + "form", + "componentList", + "componentListItem", + "outline", + "puck", +] as const; + +type CustomUiGeneric< + Shape extends { [key in (typeof customUiKeys)[number]]: any } +> = Shape; + +export type CustomUi = CustomUiGeneric<{ + fields: Partial; + header: RenderFunc<{ actions: ReactNode; children: ReactNode }>; + headerActions: RenderFunc<{}>; + root: RenderFunc; + rootForm: RenderFunc<{ children: ReactNode; isLoading: boolean }>; + form: RenderFunc<{ children: ReactNode; isLoading: boolean }>; + componentList: RenderFunc; + componentListItem: RenderFunc<{ children: ReactNode; name: string }>; + outline: RenderFunc; + puck: RenderFunc; +}>; + +export type FieldRenderFunctions = Omit< + { + [Type in Field["type"]]: ( + props: InputProps> + ) => ReactNode; + }, + "custom" +> & { [key: string]: (props: InputProps) => ReactNode }; diff --git a/packages/core/types/Plugin.ts b/packages/core/types/Plugin.ts index 3b6419350a..4340ab0098 100644 --- a/packages/core/types/Plugin.ts +++ b/packages/core/types/Plugin.ts @@ -1,21 +1,5 @@ -import { ReactElement, ReactNode } from "react"; -import { AppState } from "./Config"; -import { PuckAction } from "../reducer"; +import { CustomUi } from "./CustomUi"; export type Plugin = { - renderRootFields?: (props: { - children: ReactNode; - dispatch: (action: PuckAction) => void; - state: AppState; - }) => ReactElement; - renderRoot?: (props: { - children: ReactNode; - dispatch: (action: PuckAction) => void; - state: AppState; - }) => ReactElement; - renderFields?: (props: { - children: ReactNode; - dispatch: (action: PuckAction) => void; - state: AppState; - }) => ReactElement; + customUi: Partial; }; diff --git a/packages/plugin-heading-analyzer/src/HeadingAnalyzer.tsx b/packages/plugin-heading-analyzer/src/HeadingAnalyzer.tsx index 1663f38b5e..3c28a53081 100644 --- a/packages/plugin-heading-analyzer/src/HeadingAnalyzer.tsx +++ b/packages/plugin-heading-analyzer/src/HeadingAnalyzer.tsx @@ -1,6 +1,6 @@ -import { ReactElement, ReactNode, useEffect, useState } from "react"; +import { ReactElement, useEffect, useState } from "react"; -import { AppState } from "@/core"; +import { usePuck } from "@/core"; import { Plugin } from "@/core/types/Plugin"; import { SidebarSection } from "@/core/components/SidebarSection"; import { OutlineList } from "@/core/components/OutlineList"; @@ -8,7 +8,6 @@ import { OutlineList } from "@/core/components/OutlineList"; import { scrollIntoView } from "@/core/lib/scroll-into-view"; import ReactFromJSON from "react-from-json"; -import { PuckAction } from "@/core/reducer"; const dataAttr = "data-puck-heading-analyzer-id"; @@ -88,15 +87,8 @@ function buildHierarchy(): Block[] { return root.children; } -const HeadingOutlineAnalyer = ({ - children, - state, -}: { - children: ReactNode; - state: AppState; - dispatch: (action: PuckAction) => void; -}) => { - const { data } = state; +export const HeadingAnalyzer = () => { + const { appState } = usePuck(); const [hierarchy, setHierarchy] = useState([]); const [firstRender, setFirstRender] = useState(true); @@ -112,91 +104,97 @@ const HeadingOutlineAnalyer = ({ } else { setHierarchy(buildHierarchy()); } - }, [data.content]); + }, [appState.data.content]); return ( <> - {children} - - {hierarchy.length === 0 &&
No headings.
} - - - ReactElement; - OutlineListItem: (any) => ReactElement; - }> - mapping={{ - Root: (props) => <>{props.children}, - OutlineListItem: (props) => ( - - - { - e.stopPropagation(); - - const el = document.querySelector( - `[${dataAttr}="${props.analyzeId}"]` - ) as HTMLElement; - - const oldStyle = { ...el.style }; - - if (el) { - scrollIntoView(el); - - el.style.outline = - "4px solid var(--puck-color-rose-5)"; - el.style.outlineOffset = "4px"; - - setTimeout(() => { - el.style.outline = oldStyle.outline || ""; - el.style.outlineOffset = - oldStyle.outlineOffset || ""; - }, 2000); - } + {hierarchy.length === 0 &&
No headings.
} + + + ReactElement; + OutlineListItem: (any) => ReactElement; + }> + mapping={{ + Root: (props) => <>{props.children}, + OutlineListItem: (props) => ( + + + { + e.stopPropagation(); + + const el = document.querySelector( + `[${dataAttr}="${props.analyzeId}"]` + ) as HTMLElement; + + const oldStyle = { ...el.style }; + + if (el) { + scrollIntoView(el); + + el.style.outline = + "4px solid var(--puck-color-rose-5)"; + el.style.outlineOffset = "4px"; + + setTimeout(() => { + el.style.outline = oldStyle.outline || ""; + el.style.outlineOffset = + oldStyle.outlineOffset || ""; + }, 2000); } - } - > - {props.missing ? ( - - H{props.rank}: Missing - - ) : ( - - H{props.rank}: {props.text} - - )} - - - {props.children} - - ), - }} - entry={{ - props: { children: hierarchy }, - type: "Root", - }} - mapProp={(prop) => { - if (prop && prop.rank) { - return { - type: "OutlineListItem", - props: prop, - }; - } - - return prop; - }} - /> - -
+ } + } + > + {props.missing ? ( + + H{props.rank}: Missing + + ) : ( + + H{props.rank}: {props.text} + + )} + + + {props.children} + + ), + }} + entry={{ + props: { children: hierarchy }, + type: "Root", + }} + mapProp={(prop) => { + if (prop && prop.rank) { + return { + type: "OutlineListItem", + props: prop, + }; + } + + return prop; + }} + /> + ); }; -const HeadingAnalyzer: Plugin = { - renderRootFields: HeadingOutlineAnalyer, +const headingAnalyzer: Plugin = { + customUi: { + rootForm: ({ children }) => ( + <> + {children} + + + + + ), + }, }; -export default HeadingAnalyzer; +export default headingAnalyzer;