feat(editor): add reserveToolbarSpace option to enhance toolbar visibility management

This commit is contained in:
Anish Sarkar 2026-04-23 20:13:29 +05:30
parent 0381632bc2
commit a1d3356bf5
5 changed files with 36 additions and 4 deletions

View file

@ -572,6 +572,7 @@ export function EditorPanelContent({
placeholder="Start writing..." placeholder="Start writing..."
editorVariant="default" editorVariant="default"
allowModeToggle={false} allowModeToggle={false}
reserveToolbarSpace
defaultEditing={isEditing} defaultEditing={isEditing}
className="[&_[role=toolbar]]:!bg-sidebar" className="[&_[role=toolbar]]:!bg-sidebar"
/> />

View file

@ -11,12 +11,15 @@ interface EditorSaveContextValue {
isSaving: boolean; isSaving: boolean;
/** Whether the user can toggle between editing and viewing modes */ /** Whether the user can toggle between editing and viewing modes */
canToggleMode: boolean; canToggleMode: boolean;
/** Whether fixed-toolbar space should be reserved even when controls are hidden */
reserveToolbarSpace: boolean;
} }
export const EditorSaveContext = createContext<EditorSaveContextValue>({ export const EditorSaveContext = createContext<EditorSaveContextValue>({
hasUnsavedChanges: false, hasUnsavedChanges: false,
isSaving: false, isSaving: false,
canToggleMode: false, canToggleMode: false,
reserveToolbarSpace: false,
}); });
export function useEditorSave() { export function useEditorSave() {

View file

@ -44,6 +44,8 @@ export interface PlateEditorProps {
isSaving?: boolean; isSaving?: boolean;
/** Whether edit/view mode toggle UI should be available in toolbars. */ /** Whether edit/view mode toggle UI should be available in toolbars. */
allowModeToggle?: boolean; allowModeToggle?: boolean;
/** Reserve fixed-toolbar vertical space even when controls are hidden. */
reserveToolbarSpace?: boolean;
/** Start the editor in editing mode instead of viewing mode. Ignored when readOnly is true. */ /** Start the editor in editing mode instead of viewing mode. Ignored when readOnly is true. */
defaultEditing?: boolean; defaultEditing?: boolean;
/** /**
@ -94,6 +96,7 @@ export function PlateEditor({
hasUnsavedChanges = false, hasUnsavedChanges = false,
isSaving = false, isSaving = false,
allowModeToggle = true, allowModeToggle = true,
reserveToolbarSpace = false,
defaultEditing = false, defaultEditing = false,
preset = "full", preset = "full",
extraPlugins = [], extraPlugins = [],
@ -185,8 +188,9 @@ export function PlateEditor({
hasUnsavedChanges, hasUnsavedChanges,
isSaving, isSaving,
canToggleMode, canToggleMode,
reserveToolbarSpace,
}), }),
[onSave, hasUnsavedChanges, isSaving, canToggleMode] [onSave, hasUnsavedChanges, isSaving, canToggleMode, reserveToolbarSpace]
); );
return ( return (

View file

@ -9,12 +9,19 @@ import { FixedToolbarButtons } from "@/components/ui/fixed-toolbar-buttons";
function ConditionalFixedToolbar() { function ConditionalFixedToolbar() {
const readOnly = useEditorReadOnly(); const readOnly = useEditorReadOnly();
const { onSave, hasUnsavedChanges, canToggleMode } = useEditorSave(); const { onSave, hasUnsavedChanges, canToggleMode, reserveToolbarSpace } = useEditorSave();
const hasVisibleControls = const hasVisibleControls =
!readOnly || canToggleMode || (!!onSave && hasUnsavedChanges && !readOnly); !readOnly || canToggleMode || (!!onSave && hasUnsavedChanges && !readOnly);
if (!hasVisibleControls) return null; if (!hasVisibleControls) {
if (!reserveToolbarSpace) return null;
return (
<FixedToolbar className="pointer-events-none opacity-0">
<div className="h-8 w-full" />
</FixedToolbar>
);
}
return ( return (
<FixedToolbar> <FixedToolbar>

View file

@ -116,6 +116,7 @@ export function ReportPanelContent({
const [exporting, setExporting] = useState<string | null>(null); const [exporting, setExporting] = useState<string | null>(null);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const copyTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined); const copyTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
const changeCountRef = useRef(0);
useEffect(() => { useEffect(() => {
return () => { return () => {
@ -190,8 +191,21 @@ export function ReportPanelContent({
useEffect(() => { useEffect(() => {
setEditedMarkdown(null); setEditedMarkdown(null);
setIsEditing(false); setIsEditing(false);
changeCountRef.current = 0;
}, [activeReportId]); }, [activeReportId]);
const handleReportMarkdownChange = useCallback(
(nextMarkdown: string) => {
if (!isEditing) return;
changeCountRef.current += 1;
// Plate may emit an initial normalize/serialize change on mount.
if (changeCountRef.current <= 1) return;
const savedMarkdown = reportContent?.content ?? "";
setEditedMarkdown(nextMarkdown === savedMarkdown ? null : nextMarkdown);
},
[isEditing, reportContent?.content]
);
// Copy markdown content (uses latest editor content) // Copy markdown content (uses latest editor content)
const handleCopy = useCallback(async () => { const handleCopy = useCallback(async () => {
if (!currentMarkdown) return; if (!currentMarkdown) return;
@ -299,6 +313,7 @@ export function ReportPanelContent({
const handleCancelEditing = useCallback(() => { const handleCancelEditing = useCallback(() => {
setEditedMarkdown(null); setEditedMarkdown(null);
changeCountRef.current = 0;
setIsEditing(false); setIsEditing(false);
}, []); }, []);
@ -436,6 +451,7 @@ export function ReportPanelContent({
className="size-6" className="size-6"
onClick={() => { onClick={() => {
setEditedMarkdown(null); setEditedMarkdown(null);
changeCountRef.current = 0;
setIsEditing(true); setIsEditing(true);
}} }}
> >
@ -473,11 +489,12 @@ export function ReportPanelContent({
key={`report-${activeReportId}-${isEditing ? "editing" : "viewing"}`} key={`report-${activeReportId}-${isEditing ? "editing" : "viewing"}`}
preset="full" preset="full"
markdown={reportContent.content} markdown={reportContent.content}
onMarkdownChange={setEditedMarkdown} onMarkdownChange={handleReportMarkdownChange}
readOnly={!isEditing} readOnly={!isEditing}
placeholder="Report content..." placeholder="Report content..."
editorVariant="default" editorVariant="default"
allowModeToggle={false} allowModeToggle={false}
reserveToolbarSpace
defaultEditing={isEditing} defaultEditing={isEditing}
className="[&_[role=toolbar]]:!bg-sidebar" className="[&_[role=toolbar]]:!bg-sidebar"
/> />