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}
-
);
};
+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 (
);
};
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;