From 2154b0d025bf575b3d8a12c2f09ceae4ee58003a Mon Sep 17 00:00:00 2001 From: kraysent Date: Wed, 2 Sep 2026 22:29:00 +0100 Subject: [PATCH 1/6] some follow-up improvements for references editing --- apps/admin/src/components/ui/EditableField.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/admin/src/components/ui/EditableField.tsx b/apps/admin/src/components/ui/EditableField.tsx index 00447a4..f8c1983 100644 --- a/apps/admin/src/components/ui/EditableField.tsx +++ b/apps/admin/src/components/ui/EditableField.tsx @@ -1,6 +1,6 @@ import classNames from "classnames"; import { ReactElement, useEffect, useState } from "react"; -import { MdEdit } from "react-icons/md"; +import { MdClose, MdEdit, MdKeyboardReturn } from "react-icons/md"; import { formatCaughtError } from "@leda/lib/tap"; import { FieldInput, type FieldInputConfig } from "./FieldInput"; @@ -133,6 +133,9 @@ function EditableFieldInner({ } if (editing) { + const saveShortcut = + input.kind === "textarea" || input.kind === "json" ? "⌘↵" : "↵"; + return (
({ onCancel={cancelEdit} commitOnChange={input.kind === "select"} /> + {input.kind !== "select" ? ( +
+ + {saveShortcut} save + + + Esc cancel + +
+ ) : null} {error ?

{error}

: null}
); From 6ccb5cef799e693a77cb80b0ae7730bdb6276d58 Mon Sep 17 00:00:00 2001 From: kraysent Date: Wed, 2 Sep 2026 22:36:32 +0100 Subject: [PATCH 2/6] monaco editor fo jsons --- .../admin/src/components/ui/EditableField.tsx | 6 +- apps/admin/src/components/ui/FieldInput.tsx | 93 ++++++++++++++++++- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/apps/admin/src/components/ui/EditableField.tsx b/apps/admin/src/components/ui/EditableField.tsx index f8c1983..451404b 100644 --- a/apps/admin/src/components/ui/EditableField.tsx +++ b/apps/admin/src/components/ui/EditableField.tsx @@ -1,6 +1,6 @@ import classNames from "classnames"; import { ReactElement, useEffect, useState } from "react"; -import { MdClose, MdEdit, MdKeyboardReturn } from "react-icons/md"; +import { MdEdit } from "react-icons/md"; import { formatCaughtError } from "@leda/lib/tap"; import { FieldInput, type FieldInputConfig } from "./FieldInput"; @@ -155,9 +155,7 @@ function EditableFieldInner({ {saveShortcut} save - - Esc cancel - + Esc cancel ) : null} {error ?

{error}

: null} diff --git a/apps/admin/src/components/ui/FieldInput.tsx b/apps/admin/src/components/ui/FieldInput.tsx index da44e19..5313ca1 100644 --- a/apps/admin/src/components/ui/FieldInput.tsx +++ b/apps/admin/src/components/ui/FieldInput.tsx @@ -7,7 +7,9 @@ import { useRef, useState, } from "react"; -import { SuggestibleInput } from "@leda/lib/ui"; +import Editor from "@monaco-editor/react"; +import type * as Monaco from "monaco-editor"; +import { SuggestibleInput, useTheme } from "@leda/lib/ui"; const SEARCH_DEBOUNCE_MS = 300; @@ -103,6 +105,81 @@ function handleMultilineKeyDown( } } +interface JsonEditorProps { + value: string; + onChange: (value: string) => void; + disabled: boolean; + autoFocus: boolean; + className: string; + onSave?: () => void; + onCancel?: () => void; +} + +function JsonEditor({ + value, + onChange, + disabled, + autoFocus, + className, + onSave, + onCancel, +}: JsonEditorProps): ReactElement { + const { effectiveTheme } = useTheme(); + const onSaveRef = useRef(onSave); + const onCancelRef = useRef(onCancel); + const height = `${(value.split("\n").length + 1) * 20 + 8}px`; + + onSaveRef.current = onSave; + onCancelRef.current = onCancel; + + return ( +
event.stopPropagation()} + > + onChange(nextValue ?? "")} + onMount={(editor, monaco: typeof Monaco) => { + if (autoFocus) { + editor.focus(); + } + editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => + onSaveRef.current?.(), + ); + editor.addCommand(monaco.KeyCode.Escape, () => { + onCancelRef.current?.(); + }); + }} + options={{ + readOnly: disabled, + minimap: { enabled: false }, + fontSize: 13, + lineNumbers: "off", + folding: false, + glyphMargin: false, + overviewRulerLanes: 0, + renderLineHighlight: "none", + scrollBeyondLastLine: false, + wordWrap: "on", + automaticLayout: true, + tabSize: 2, + padding: { top: 4, bottom: 4 }, + scrollbar: { + horizontal: "hidden", + vertical: "hidden", + handleMouseWheel: false, + }, + }} + /> +
+ ); +} + interface AutocompleteInputProps { loadOptions: (query: string) => Promise; value: string; @@ -277,6 +354,20 @@ export function FieldInput({ ); } + if (input.kind === "json" && appearance === "inline") { + return ( + + ); + } + if (input.kind === "textarea" || input.kind === "json") { return (