feat(workflow): Monaco editor для proposed schema (C)

This commit is contained in:
Александр Зимин
2026-05-12 11:41:12 +00:00
parent 47fd9f85af
commit ef5bd69c40
5 changed files with 149 additions and 8 deletions
@@ -11,6 +11,7 @@ import {
import { ArrowsClockwiseIcon, CheckSquareIcon } from '@phosphor-icons/react'
import type { DictionaryDetail, SchemaDraft } from '@/api/client'
import { useCreateSchemaDraft, useUpdateSchemaDraft } from '@/api/mutations'
import { JsonEditor } from '@/components/workflow/JsonEditor'
type Props = {
open: boolean
@@ -247,18 +248,18 @@ export function CreateSchemaDraftModal({ open, onClose, detail, editDraft, onCre
</Button>
</div>
</div>
{/* Custom monospace textarea — для full Monaco editor'а Phase 1d.
Высота min-h-[400px] чтобы средняя schema ~30-50 полей помещалась
без скролла. */}
<textarea
{/* Monaco editor — lazy-loaded (~1.2MB gzip). Syntax highlight,
bracket matching, fold/unfold по структуре JSON. Высота 420px —
средняя schema (~30-50 полей) помещается без вертикального
скролла. */}
<JsonEditor
value={json}
onChange={(e) => {
setJson(e.target.value)
onChange={(v) => {
setJson(v)
if (parseError) setParseError(null)
}}
spellCheck={false}
disabled={pending}
className="w-full min-h-[400px] max-h-[60vh] text-mono text-cell rounded-md border border-line bg-[#0c0a23] text-[#f0eaff] p-3 focus:outline-none focus:ring-1 focus:ring-accent"
height={420}
/>
{parseError && (
<Alert variant="error" className="mt-2">
@@ -0,0 +1,73 @@
import { lazy, Suspense } from 'react'
import { useTranslation } from 'react-i18next'
import { LoadingBlock } from '@/ui'
const MonacoEditor = lazy(() => import('@monaco-editor/react').then((m) => ({ default: m.Editor })))
type Props = {
value: string
onChange: (v: string) => void
disabled?: boolean
/** Pixel height; default 400. Используется minHeight CSS — editor сам
* растягивается до этого значения. */
height?: number | string
}
/**
* Lazy-loaded Monaco editor для JSON. Bundle ~1.2MB gzip — lazy через
* dynamic import. Suspense fallback показывает loading block.
*
* <p>Theme подставляется fixed 'vs-dark' чтобы match'илось с нашим
* space-theme background (#0c0a23). Monaco поддерживает custom themes
* через monaco.editor.defineTheme — это можно добавить отдельно если
* захочется precise color match.
*
* <p>Options:
* <ul>
* <li>JSON language с syntax highlighting + bracket matching.</li>
* <li>Auto-format on paste/type, format on save (через
* monaco.languages.json.jsonDefaults).</li>
* <li>Minimap скрыт — narrow modal viewport, minimap занимает место.</li>
* <li>Read-only режим через disabled prop.</li>
* </ul>
*/
export function JsonEditor({ value, onChange, disabled = false, height = 400 }: Props) {
const { t } = useTranslation()
return (
<Suspense fallback={<LoadingBlock size="md" label={t('loading')} />}>
<div
className="rounded-md border border-line overflow-hidden bg-[#0c0a23]"
style={{ height: typeof height === 'number' ? `${height}px` : height }}
>
<MonacoEditor
height="100%"
defaultLanguage="json"
language="json"
value={value}
onChange={(v) => onChange(v ?? '')}
theme="vs-dark"
options={{
readOnly: disabled,
minimap: { enabled: false },
fontSize: 13,
fontFamily:
'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
lineNumbers: 'on',
renderLineHighlight: 'line',
scrollBeyondLastLine: false,
wordWrap: 'on',
tabSize: 2,
insertSpaces: true,
formatOnPaste: true,
formatOnType: true,
bracketPairColorization: { enabled: true },
// Monaco autoclosing для JSON: brackets/quotes на open auto-close,
// помогает писать большие schema'ы без потери indentation.
autoClosingBrackets: 'always',
autoClosingQuotes: 'always',
}}
/>
</div>
</Suspense>
)
}