> For AI agents: the complete documentation index is available at https://wallarm.github.io/design-system/llms.txt, the full documentation bundle is available at https://wallarm.github.io/design-system/llms-full.txt.

# CodeEditor

An editable code surface that is visually the same component as `CodeSnippet` and reuses its header, tabs, actions and show-more — reach for `CodeSnippet` whenever the code is only read, since a page that never renders `CodeEditorContent` never loads the editor engine. Colours come from the same `CodeSnippetAdapterProvider` (plain text without one), so a read-only editor matches the snippet except for a native thin scrollbar, a caret and selection drawn by the editor, and an HTTP JSON body that stays coloured when `Content-Type` has parameters or the body has blank lines. The adapter re-tokenizes the whole document after each pause in typing, which stays within a frame up to about 2 000 lines with Prism; longer documents still edit smoothly, their colours just settle a moment later. Like the snippet, the editor sits inside a `display: contents` wrapper that keeps it mounted across fullscreen, so space editors with their own `className` (for example `mt-16`) or a `gap` on the parent — not `space-*`, `divide-*` or child selectors on the parent. `lines`, `folds`, `completions` and `schema` are compared by identity, so memoise them or hoist them to module constants — a new value on every render reconfigures the editor. Autocomplete suggests language keywords and words already in the document for every language; HTTP methods and headers and JSON Schema values have their own contextual suggestions. Pass `completions` to add application-specific results. Syntax errors are best-effort: JSON reports the `JSON.parse` message, JavaScript and TypeScript use Babel, Lua uses luaparse (first error only), YAML and Python use their Lezer parsers, which still flag a few valid Python forms such as `lambda a, /, b` and parenthesised `with` items. `lines` and `folds` stay on their line numbers while typing; to keep a decoration on a piece of text, recompute it from `value` (for example `useMemo(() => linesFor(value), [value])`, or a `folds` function such as `getHttpFolds`). The editor injects `<style>` tags, so under a strict Content Security Policy pass `cspNonce`. Tab indents, so keyboard users leave with Escape, then Tab, or press Ctrl+M (Alt+Shift+M on macOS) to switch Tab between indenting and moving focus — point `aria-describedby` at text holding `CODE_EDITOR_KEYBOARD_HINT` to tell screen-reader users.

[Source](https://github.com/wallarm/design-system/tree/main/packages/design-system/src/components/CodeEditor) · [Storybook](https://wallarm.github.io/design-system/storybook/?path=/docs/data-display-codeeditor--overview)

```tsx
import { useState } from 'react';
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';

export const Default = () => {
  const [value, setValue] = useState(jsonCode);

  return (
    <div style={{ width: '600px' }}>
      <CodeEditorRoot
        value={value}
        onChange={setValue}
        language='json'
        data-testid='code-editor-default'
      >
        <CodeEditorContent lineNumbers aria-label='Package manifest' />
      </CodeEditorRoot>
    </div>
  );
};
```

## Usage

```tsx
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';
```

## Examples

### With header

The `CodeSnippet` header works unchanged: a title and the copy, wrap and fullscreen buttons.
Copy puts the edited value on the clipboard, and fullscreen keeps the undo history.

```tsx
import { useState } from 'react';
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';
import { CodeSnippetActions, CodeSnippetCopyButton, CodeSnippetFullscreenButton, CodeSnippetHeader, CodeSnippetTitle, CodeSnippetWrapButton } from '@wallarm-org/design-system/CodeSnippet';

export const WithHeader = () => {
  const [value, setValue] = useState(jsonCode);

  return (
    <div style={{ width: '600px' }}>
      <CodeEditorRoot
        value={value}
        onChange={setValue}
        language='json'
        data-testid='code-editor-with-header'
      >
        <CodeSnippetHeader>
          <CodeSnippetTitle>rule.json</CodeSnippetTitle>
          <CodeSnippetActions>
            <CodeSnippetCopyButton />
            <CodeSnippetWrapButton />
            <CodeSnippetFullscreenButton />
          </CodeSnippetActions>
        </CodeSnippetHeader>
        <CodeEditorContent lineNumbers aria-label='Rule JSON' />
      </CodeEditorRoot>
    </div>
  );
};
```

### Floating actions

Without a header, `CodeSnippetActions` as a direct child floats over the top-right of the
editor, as it does over a snippet.

```tsx
import { useState } from 'react';
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';
import { CodeSnippetActions, CodeSnippetCopyButton, CodeSnippetWrapButton } from '@wallarm-org/design-system/CodeSnippet';

export const FloatingActions = () => {
  const [value, setValue] = useState(sampleCode);

  return (
    <div style={{ width: '320px' }}>
      <CodeEditorRoot
        value={value}
        onChange={setValue}
        language='text'
        data-testid='code-editor-floating-actions'
      >
        <CodeSnippetActions>
          <CodeSnippetWrapButton />
          <CodeSnippetCopyButton />
        </CodeSnippetActions>
        <CodeEditorContent lineNumbers aria-label='Sample code' />
      </CodeEditorRoot>
    </div>
  );
};
```

### Line numbers and starting line

`lineNumbers` on `CodeEditorContent` is the equivalent of `CodeSnippetLineNumbers`, and
`startingLineNumber` renumbers the gutter for a fragment taken from a longer file.

```tsx
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';
import { VStack } from '@wallarm-org/design-system/Stack';

export const LineNumbersAndStartingLine = () => (
  <VStack gap={16}>
    <VStack align='start' gap={4}>
      <div style={{ width: '600px' }}>
        <CodeEditorRoot
          defaultValue={sampleCode}
          language='text'
          data-testid='code-editor-line-numbers'
        >
          <CodeEditorContent lineNumbers aria-label='Sample code' />
        </CodeEditorRoot>
      </div>
    </VStack>
    <VStack align='start' gap={4}>
      <div style={{ width: '600px' }}>
        <CodeEditorRoot
          defaultValue={sampleCode}
          language='text'
          startingLineNumber={97}
          data-testid='code-editor-starting-line'
        >
          <CodeEditorContent lineNumbers aria-label='Sample code from line 97' />
        </CodeEditorRoot>
      </div>
    </VStack>
  </VStack>
);
```

### Line colors and prefixes

`lines` uses the same `LineConfig` as CodeSnippet: seven semantic colours, text prefixes for a
hand-made diff, and ReactNode prefixes such as a tooltipped icon. Lines are static and tied
to line numbers, so they stay on their number while you type.

```tsx
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';
import { VStack } from '@wallarm-org/design-system/Stack';

export const LineColorsAndPrefixes = () => (
  <VStack gap={16}>
    <VStack align='start' gap={4}>
      <div style={{ width: '600px' }}>
        <CodeEditorRoot
          defaultValue={colorsCode}
          language='text'
          lines={colorLines}
          data-testid='code-editor-line-colors'
        >
          <CodeEditorContent lineNumbers aria-label='Line colours' />
        </CodeEditorRoot>
      </div>
    </VStack>
    <VStack align='start' gap={4}>
      <div style={{ width: '600px' }}>
        <CodeEditorRoot
          defaultValue={diffCode}
          language='text'
          lines={diffLines}
          data-testid='code-editor-line-diff'
        >
          <CodeEditorContent lineNumbers aria-label='Diff prefixes' />
        </CodeEditorRoot>
      </div>
    </VStack>
    <VStack align='start' gap={4}>
      <div style={{ width: '600px' }}>
        <CodeEditorRoot
          defaultValue={annotatedHeadersCode}
          language='text'
          lines={iconLines}
          data-testid='code-editor-line-icons'
        >
          <CodeEditorContent lineNumbers aria-label='Annotated headers' />
        </CodeEditorRoot>
      </div>
    </VStack>
  </VStack>
);
```

### Ranges

`ranges` colours character offsets inside a line (start inclusive, end exclusive). The line
keeps its background, and only the range gets the colour.

```tsx
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';

export const Ranges = () => (
  <div style={{ width: '600px' }}>
    <CodeEditorRoot
      defaultValue={rangesCode}
      language='text'
      lines={rangeLines}
      data-testid='code-editor-ranges'
    >
      <CodeEditorContent lineNumbers aria-label='Request with ranges' />
    </CodeEditorRoot>
  </div>
);
```

### Http request with prism

`language='http'` under Prism. The colours come from the adapter and the structure (start
line, headers, JSON body) comes from the editor's own HTTP grammar.

```tsx
import { useState } from 'react';
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';
import { CodeSnippetAdapterProvider, loadPrismAdapter } from '@wallarm-org/design-system/CodeSnippet';

export const HttpRequestWithPrism = () => {
  const [value, setValue] = useState(httpRequestCode);

  return (
    <CodeSnippetAdapterProvider adapter={loadPrismAdapter}>
      <div style={{ width: '600px' }}>
        <CodeEditorRoot
          value={value}
          onChange={setValue}
          language='http'
          data-testid='code-editor-http-request'
        >
          <CodeEditorContent lineNumbers aria-label='HTTP request' />
        </CodeEditorRoot>
      </div>
    </CodeSnippetAdapterProvider>
  );
};
```

### Http response with shiki

The response side under Shiki. After an edit, highlighting is repainted in the background,
so for a moment you may see the previous colours mapped through your change.

```tsx
import { useState } from 'react';
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';
import { CodeSnippetAdapterProvider, loadShikiAdapter } from '@wallarm-org/design-system/CodeSnippet';

export const HttpResponseWithShiki = () => {
  const [value, setValue] = useState(httpResponseCode);

  return (
    <CodeSnippetAdapterProvider adapter={loadShikiAdapter}>
      <div style={{ width: '600px' }}>
        <CodeEditorRoot
          value={value}
          onChange={setValue}
          language='http'
          data-testid='code-editor-http-response'
        >
          <CodeEditorContent lineNumbers aria-label='HTTP response' />
        </CodeEditorRoot>
      </div>
    </CodeSnippetAdapterProvider>
  );
};
```

### Http folds

`folds` as a function of the value: `getHttpFolds` runs again after edits, so adding a header
grows the headers fold. Collapsed state is kept by fold `id`. The second editor starts at
line 100, and the fold function gets the same offset.

```tsx
import { useState } from 'react';
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';
import { VStack } from '@wallarm-org/design-system/Stack';

export const HttpFolds = () => {
  const [request, setRequest] = useState(foldableRequestCode);
  const [response, setResponse] = useState(foldableResponseCode);
  return (
    <VStack gap={16}>
      <VStack align='start' gap={4}>
        <div style={{ width: '600px' }}>
          <CodeEditorRoot
            value={request}
            onChange={setRequest}
            language='http'
            folds={foldRequestHeadersCollapsed}
            data-testid='code-editor-http-folds-request'
          >
            <CodeEditorContent lineNumbers aria-label='Foldable HTTP request' />
          </CodeEditorRoot>
        </div>
      </VStack>
      <VStack align='start' gap={4}>
        <div style={{ width: '600px' }}>
          <CodeEditorRoot
            value={response}
            onChange={setResponse}
            language='http'
            startingLineNumber={100}
            folds={foldHttp}
            data-testid='code-editor-http-folds-response'
          >
            <CodeEditorContent lineNumbers aria-label='Foldable HTTP response' />
          </CodeEditorRoot>
        </div>
      </VStack>
    </VStack>
  );
};
```

### Show more

`maxLines` limits the height, and the editor scrolls inside it (no rows are removed). The
button appears on its own when at least three lines are hidden. Render
`CodeSnippetShowMoreButton` yourself only to pass props to the real button.

```tsx
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';
import { CodeSnippetShowMoreButton } from '@wallarm-org/design-system/CodeSnippet';

export const ShowMore = () => (
  <div style={{ display: 'flex', flexDirection: 'column', gap: '24px', width: '500px' }}>
    <div>
      <CodeEditorRoot
        defaultValue={showMoreCode}
        language='text'
        maxLines={7}
        data-testid='code-editor-show-more'
      >
        <CodeEditorContent aria-label='Request headers' />
      </CodeEditorRoot>
    </div>
    <div>
      <CodeEditorRoot
        defaultValue={showMoreThresholdCode}
        language='text'
        maxLines={7}
        data-testid='code-editor-show-more-threshold'
      >
        <CodeEditorContent aria-label='Request headers' />
      </CodeEditorRoot>
    </div>
    <div>
      <CodeEditorRoot
        defaultValue={showMoreCode}
        language='text'
        maxLines={7}
        data-testid='code-editor-show-more-manual'
      >
        <CodeEditorContent aria-label='Request headers' />
        <CodeSnippetShowMoreButton data-analytics-id='CODE_EDITOR_SHOW_MORE' />
      </CodeEditorRoot>
    </div>
  </div>
);
```

### Wrap

Wrapping can be uncontrolled (`defaultWrapLines`, driven by the wrap button) or controlled
(`wrapLines` plus `onWrapLinesChange`). Colour sticks and prefixes stay on the first visual row.

```tsx
import { useState } from 'react';
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';
import { CodeSnippetActions, CodeSnippetWrapButton } from '@wallarm-org/design-system/CodeSnippet';
import { VStack } from '@wallarm-org/design-system/Stack';

export const Wrap = () => {
  const [wrap, setWrap] = useState(true);
  return (
    <VStack gap={16}>
      <VStack align='start' gap={4}>
        <div style={{ width: '600px' }}>
          <CodeEditorRoot
            defaultValue={longCode}
            language='text'
            lines={wrapDiffLines}
            data-testid='code-editor-wrap-off'
          >
            <CodeEditorContent lineNumbers aria-label='Long lines' />
          </CodeEditorRoot>
        </div>
      </VStack>
      <VStack align='start' gap={4}>
        <div style={{ width: '600px' }}>
          <CodeEditorRoot
            defaultValue={longCode}
            language='text'
            defaultWrapLines
            lines={wrapDiffLines}
            data-testid='code-editor-wrap-uncontrolled'
          >
            <CodeSnippetActions>
              <CodeSnippetWrapButton />
            </CodeSnippetActions>
            <CodeEditorContent lineNumbers aria-label='Long lines, wrapped' />
          </CodeEditorRoot>
        </div>
      </VStack>
      <VStack align='start' gap={4}>
        <div style={{ width: '600px' }}>
          <CodeEditorRoot
            defaultValue={longCode}
            language='text'
            wrapLines={wrap}
            onWrapLinesChange={setWrap}
            data-testid='code-editor-wrap-controlled'
          >
            <CodeSnippetActions>
              <CodeSnippetWrapButton />
            </CodeSnippetActions>
            <CodeEditorContent lineNumbers aria-label='Long lines, controlled wrap' />
          </CodeEditorRoot>
        </div>
      </VStack>
    </VStack>
  );
};
```

### Sizes

The same three sizes as CodeSnippet: `sm` (12px, default), `md` (14px) and `lg` (16px), each
on 20px rows.

```tsx
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';
import { VStack } from '@wallarm-org/design-system/Stack';

export const Sizes = () => (
  <VStack gap={16}>
    <VStack align='start' gap={4}>
      <div style={{ width: '600px' }}>
        <CodeEditorRoot
          defaultValue={bashCode}
          language='bash'
          size='sm'
          data-testid='code-editor-size-sm'
        >
          <CodeEditorContent lineNumbers aria-label='Install commands, small' />
        </CodeEditorRoot>
      </div>
    </VStack>
    <VStack align='start' gap={4}>
      <div style={{ width: '600px' }}>
        <CodeEditorRoot
          defaultValue={bashCode}
          language='bash'
          size='md'
          data-testid='code-editor-size-md'
        >
          <CodeEditorContent lineNumbers aria-label='Install commands, medium' />
        </CodeEditorRoot>
      </div>
    </VStack>
    <VStack align='start' gap={4}>
      <div style={{ width: '600px' }}>
        <CodeEditorRoot
          defaultValue={bashCode}
          language='bash'
          size='lg'
          data-testid='code-editor-size-lg'
        >
          <CodeEditorContent lineNumbers aria-label='Install commands, large' />
        </CodeEditorRoot>
      </div>
    </VStack>
  </VStack>
);
```

### Read only

`readOnly` lets you select, search and copy without focusing the code or showing a caret. Use it when the value
can change elsewhere but not here. If it never changes, use `CodeSnippet`.

```tsx
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';
import { CodeSnippetActions, CodeSnippetCopyButton, CodeSnippetHeader, CodeSnippetTitle } from '@wallarm-org/design-system/CodeSnippet';

export const ReadOnly = () => (
  <div style={{ width: '600px' }}>
    <CodeEditorRoot
      value={httpResponseCode}
      language='http'
      readOnly
      data-testid='code-editor-read-only'
    >
      <CodeSnippetHeader>
        <CodeSnippetTitle>Response</CodeSnippetTitle>
        <CodeSnippetActions>
          <CodeSnippetCopyButton />
        </CodeSnippetActions>
      </CodeSnippetHeader>
      <CodeEditorContent lineNumbers aria-label='HTTP response, read only' />
    </CodeEditorRoot>
  </div>
);
```

### Tabs

Tabs swap `value`, and `documentId` swaps the editor's whole state with it, so each tab keeps
its own undo history, selection and collapsed folds.

```tsx
import { useState } from 'react';
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';
import { CodeSnippetActions, CodeSnippetCopyButton, CodeSnippetHeader, CodeSnippetTab, CodeSnippetTabs } from '@wallarm-org/design-system/CodeSnippet';

export const Tabs = () => {
  const [tab, setTab] = useState<TabId>('request');
  const [docs, setDocs] = useState<Record<TabId, string>>(tabDocuments);

  return (
    <div style={{ width: '600px' }}>
      <CodeEditorRoot
        documentId={tab}
        value={docs[tab]}
        onChange={value => setDocs(prev => ({ ...prev, [tab]: value }))}
        language='http'
        data-testid='code-editor-tabs'
      >
        <CodeSnippetHeader>
          <CodeSnippetTabs
            value={tab}
            onValueChange={value => {
              if (isTabId(value)) setTab(value);
            }}
          >
            <CodeSnippetTab value='request' data-testid='code-editor-tabs--tab-request'>
              Request
            </CodeSnippetTab>
            <CodeSnippetTab value='response' data-testid='code-editor-tabs--tab-response'>
              Response
            </CodeSnippetTab>
          </CodeSnippetTabs>
          <CodeSnippetActions>
            <CodeSnippetCopyButton />
          </CodeSnippetActions>
        </CodeSnippetHeader>
        <CodeEditorContent lineNumbers aria-label='HTTP message' />
      </CodeEditorRoot>
    </div>
  );
};
```

### Parity default

Parity check: the same code and props in CodeSnippet and in a read-only CodeEditor should look
identical. Accepted differences: a native thin scrollbar, and CodeMirror's caret and selection
layer (shown only while focused).

```tsx
export const ParityDefault = () => (
  <ParityPair testId='parity-default' code={sampleCode} language='text' />
);
```

### Parity line colors

Parity check with line colours, text prefixes and a tooltipped icon prefix: same stick, same
tint, same prefix column.

```tsx
export const ParityLineColors = () => (
  <ParityPair
    testId='parity-line-colors'
    code={colorsCode}
    language='text'
    lines={parityColorLines}
  />
);
```

### Parity folds collapsed

Parity check with the headers fold collapsed: same chevrons and the same summary row in place
of the hidden lines.

```tsx
export const ParityFoldsCollapsed = () => (
  <ParityPair
    testId='parity-folds-collapsed'
    code={foldableRequestCode}
    language='text'
    folds={parityCollapsedFolds}
  />
);
```

### Parity http prism

Parity check for an HTTP request with a JSON body, both under the same Prism provider: token
colours come from the same adapter.

```tsx
import { CodeSnippetAdapterProvider, loadPrismAdapter } from '@wallarm-org/design-system/CodeSnippet';

export const ParityHttpPrism = () => (
  <CodeSnippetAdapterProvider adapter={loadPrismAdapter}>
    <ParityPair testId='parity-http-prism' code={httpRequestCode} language='http' />
  </CodeSnippetAdapterProvider>
);
```

### Parity ranges

Parity check for `ranges`: the same bold, coloured spans inside the same tinted lines.

```tsx
export const ParityRanges = () => (
  <ParityPair testId='parity-ranges' code={rangesCode} language='text' lines={rangeLines} />
);
```

### Parity wrap

Parity check for wrapped lines: long lines break at the same points and line numbers stay on
the first row of each wrapped line.

```tsx
export const ParityWrap = () => (
  <ParityPair testId='parity-wrap' code={longCode} language='text' wrapLines />
);
```

### Parity sizes

Parity check for the three sizes: the same font size, line height and gutter padding per size.

```tsx
import { VStack } from '@wallarm-org/design-system/Stack';

export const ParitySizes = () => (
  <VStack gap={24}>
    <ParityPair testId='parity-size-sm' code={bashCode} language='bash' size='sm' />
    <ParityPair testId='parity-size-md' code={bashCode} language='bash' size='md' />
    <ParityPair testId='parity-size-lg' code={bashCode} language='bash' size='lg' />
  </VStack>
);
```

### Parity frame

Parity check for the shared frame: a header with tabs and actions, and floating actions
without a header, sit in the same place with the same spacing.

```tsx
import { VStack } from '@wallarm-org/design-system/Stack';

export const ParityFrame = () => (
  <VStack gap={24}>
    <ParityPair testId='parity-frame-header' code={sampleCode} language='text'>
      {PARITY_HEADER_FRAME}
    </ParityPair>
    <ParityPair testId='parity-frame-floating' code={sampleCode} language='text'>
      {PARITY_FLOATING_FRAME}
    </ParityPair>
  </VStack>
);
```

### Parity show more

Parity check for `maxLines`: the same clamped height and the same "Show N more lines" button.

```tsx
export const ParityShowMore = () => (
  <ParityPair testId='parity-show-more' code={showMoreCode} language='text' maxLines={7} />
);
```

### Languages

Colours come from the adapter (Shiki here); JavaScript, TypeScript, Python and Lua parsers load
on first use.

```tsx
import { CodeSnippetAdapterProvider, loadShikiAdapter } from '@wallarm-org/design-system/CodeSnippet';
import { VStack } from '@wallarm-org/design-system/Stack';

export const Languages = () => (
  <CodeSnippetAdapterProvider adapter={loadShikiAdapter}>
    <VStack gap={16}>
      {LANGUAGE_SAMPLES.map(sample => (
        <LanguageExample key={sample.id} sample={sample} />
      ))}
    </VStack>
  </CodeSnippetAdapterProvider>
);
```

### Syntax errors

Syntax errors for every language with a structure parser: JSON and an HTTP JSON body use the
precise `JSON.parse` message, JavaScript and TypeScript use Babel, Lua uses luaparse (first error only), YAML and Python report
Lezer error nodes. Hover an underline to read the message; the count below each editor comes from
`onDiagnosticsChange`. Bash and plain text have no syntax diagnostics.

```tsx
import { CodeSnippetAdapterProvider, loadShikiAdapter } from '@wallarm-org/design-system/CodeSnippet';
import { VStack } from '@wallarm-org/design-system/Stack';

export const SyntaxErrors = () => (
  <CodeSnippetAdapterProvider adapter={loadShikiAdapter}>
    <VStack gap={16}>
      {SYNTAX_ERROR_SAMPLES.map(sample => (
        <SyntaxErrorExample key={sample.language} sample={sample} />
      ))}
    </VStack>
  </CodeSnippetAdapterProvider>
);
```

### Diff

`original` turns on diff mode: added and changed lines read as `success` lines with a `+`,
removed lines as `danger` rows with a `-` and no line number. The rows stay editable, and the
diff updates as you type.

```tsx
import { useState } from 'react';
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';

export const Diff = () => {
  const [value, setValue] = useState(DIFF_MODIFIED);

  return (
    <CodeEditorRoot
      data-testid='code-editor-diff'
      language='http'
      original={DIFF_ORIGINAL}
      value={value}
      onChange={setValue}
    >
      <CodeEditorContent aria-label='Request draft compared with the stored request' lineNumbers />
    </CodeEditorRoot>
  );
};
```

### Diff wrap lines

With wrapping on, a removed line longer than the frame wraps inside its `danger` row and its
`-` marker stays on the first visual row, beside where the line starts.

```tsx
import { useState } from 'react';
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';

export const DiffWrapLines = () => {
  const [value, setValue] = useState(DIFF_WRAP_MODIFIED);

  return (
    <div className='w-480'>
      <CodeEditorRoot
        data-testid='code-editor-diff-wrap'
        language='http'
        original={DIFF_WRAP_ORIGINAL}
        value={value}
        onChange={setValue}
        defaultWrapLines
      >
        <CodeEditorContent aria-label='Request without the debug header' lineNumbers />
      </CodeEditorRoot>
    </div>
  );
};
```

### Analytics attributes

Where analytics attributes land. `data-*` / `aria-*` / `id` on `CodeEditorContent`
reach the typing surface (`role="textbox"`), chrome buttons take them directly, and
fold buttons take them through `toggleProps` / `summaryProps`. Popups drawn by the
editor (autocomplete, tooltips, search panel) are not attributable — see
`CodeEditor/ANALYTICS_GAPS.md`.

```tsx
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';
import { CodeSnippetActions, CodeSnippetCopyButton, CodeSnippetFullscreenButton, CodeSnippetHeader, CodeSnippetTitle, CodeSnippetWrapButton } from '@wallarm-org/design-system/CodeSnippet';

export const AnalyticsAttributes = () => (
  <CodeEditorRoot
    data-testid='analytics-editor'
    language='http'
    defaultValue={analyticsRequest}
    folds={analyticsFolds}
  >
    <CodeSnippetHeader>
      <CodeSnippetTitle>Request</CodeSnippetTitle>
      <CodeSnippetActions>
        <CodeSnippetCopyButton data-analytics-id='CODE_EDITOR_COPY' />
        <CodeSnippetWrapButton data-analytics-id='CODE_EDITOR_WRAP' />
        <CodeSnippetFullscreenButton data-analytics-id='CODE_EDITOR_FULLSCREEN' />
      </CodeSnippetActions>
    </CodeSnippetHeader>
    <CodeEditorContent
      aria-label='HTTP request'
      lineNumbers
      data-analytics-id='CODE_EDITOR_REQUEST'
      data-analytics-props='{"surface":"rule-editor"}'
    />
  </CodeEditorRoot>
);
```

### Editing workflow

A controlled HTTP request with header and body folds and a JSON Schema on the body —
`retries` breaks the schema's maximum on purpose, so the underline and its hover message
are part of the example. The editor's description points at `CODE_EDITOR_KEYBOARD_HINT`.

```tsx
import { EditingWorkflowDemo } from './story-content/_storyEditingWorkflow';

export const EditingWorkflow = () => <EditingWorkflowDemo />;
```

### Tabs keep history

Each tab passes its own `documentId`, so switching swaps in that document's undo history,
selection and collapsed folds instead of replacing the text of one shared document.

```tsx
import { TabsKeepHistoryDemo } from './story-content/_storyTabsKeepHistory';

export const TabsKeepHistory = () => <TabsKeepHistoryDemo />;
```

### Long document

2 000 read-only lines clamped by `maxLines` — the top of the envelope the colour painter is
built for, where `readOnly` still lets the text be selected, searched and copied.

```tsx
import { LongDocumentDemo } from './story-content/_storyLongDocument';

export const LongDocument = () => <LongDocumentDemo />;
```

### Fullscreen inside drawer

Fullscreen opened from a modal `Drawer` stacks above it: the editor and its buttons take
pointer and keyboard input, and Escape leaves fullscreen while the drawer stays open.

```tsx
import { useState } from 'react';
import { Button } from '@wallarm-org/design-system/Button';
import { CodeEditorContent, CodeEditorRoot } from '@wallarm-org/design-system/CodeEditor';
import { CodeSnippetActions, CodeSnippetCopyButton, CodeSnippetFullscreenButton, CodeSnippetHeader, CodeSnippetTitle } from '@wallarm-org/design-system/CodeSnippet';
import { Drawer, DrawerBody, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from '@wallarm-org/design-system/Drawer';

export const FullscreenInsideDrawer = () => {
  const [value, setValue] = useState(jsonCode);

  return (
    <Drawer>
      <DrawerTrigger asChild>
        <Button variant='outline' color='neutral'>
          Open Drawer
        </Button>
      </DrawerTrigger>
      <DrawerContent>
        <DrawerHeader>
          <DrawerTitle>Rule</DrawerTitle>
        </DrawerHeader>
        <DrawerBody>
          <CodeEditorRoot value={value} onChange={setValue} language='json'>
            <CodeSnippetHeader>
              <CodeSnippetTitle>rule.json</CodeSnippetTitle>
              <CodeSnippetActions>
                <CodeSnippetFullscreenButton />
                <CodeSnippetCopyButton />
              </CodeSnippetActions>
            </CodeSnippetHeader>
            <CodeEditorContent lineNumbers aria-label='Rule JSON' />
          </CodeEditorRoot>
        </DrawerBody>
      </DrawerContent>
    </Drawer>
  );
};
```

## Props

| Prop                | Type                                                           | Default | Description                                                                                                                                                                                                                                                                                                                              |
| ------------------- | -------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| value               | `string \| undefined`                                          |         | Controlled document                                                                                                                                                                                                                                                                                                                      |
| defaultValue        | `string \| undefined`                                          |         | Uncontrolled initial document (controlled vs uncontrolled is locked on first render)                                                                                                                                                                                                                                                     |
| onChange            | `((value: string) => void) \| undefined`                       |         | Fired for user edits only, never for external `value` syncs                                                                                                                                                                                                                                                                              |
| language            | `CodeEditorLanguage \| undefined`                              |         | Passed verbatim to the syntax adapter; selects the structural parser                                                                                                                                                                                                                                                                     |
| documentId          | `string \| undefined`                                          |         | Document identity: changing it swaps to that document's cached state (undo history, selection, folds). Requires a controlled `value`: when the id changes, `value` must already hold that document's text (an uncached id starts from `value`). In uncontrolled mode the new document would be seeded with the previous document's text. |
| readOnly            | `boolean \| undefined`                                         |         | Non-editable and unfocusable content; text remains selectable, searchable and copyable                                                                                                                                                                                                                                                   |
| startingLineNumber  | `number \| undefined`                                          |         | Offsets gutter numbers; `lines`, `folds`, `diagnostics` use absolute numbers                                                                                                                                                                                                                                                             |
| lines               | `Record<number, LineConfig> \| undefined`                      |         | Per-line configuration keyed by absolute line number (static, see spec D6). Compared by identity: memoise it (or hoist it to a constant) — a new object every render reconfigures the lines and gutters on every render.                                                                                                                 |
| folds               | `CodeEditorFolds \| undefined`                                 |         | Fold regions, or a function re-run after edits; collapsed state is kept by `id`. Compared by identity: memoise the array or pass a stable function (e.g. `getHttpFolds`) — an inline array/arrow reconfigures folds on every render and bypasses the re-run debounce.                                                                    |
| wrapLines           | `boolean \| undefined`                                         |         | Controlled line wrapping                                                                                                                                                                                                                                                                                                                 |
| defaultWrapLines    | `boolean \| undefined`                                         |         | Uncontrolled initial line wrapping                                                                                                                                                                                                                                                                                                       |
| onWrapLinesChange   | `((wrap: boolean) => void) \| undefined`                       |         | Fired when the wrap button toggles wrapping                                                                                                                                                                                                                                                                                              |
| maxLines            | `number \| undefined`                                          |         | Height clamp in rows; auto-renders `CodeSnippetShowMoreButton` unless one is a direct child                                                                                                                                                                                                                                              |
| original            | `string \| undefined`                                          |         | Enables diff mode against this text                                                                                                                                                                                                                                                                                                      |
| schema              | `JsonSchema \| undefined`                                      |         | JSON Schema for the document (`json`) or the JSON body (`http`)                                                                                                                                                                                                                                                                          |
| completions         | `CodeEditorCompletionSource[] \| undefined`                    |         | Consumer completion sources, added after the built-in ones. Compared by identity: a new array reconfigures autocomplete and closes an open list, so keep it stable (module constant or `useMemo`).                                                                                                                                       |
| diagnostics         | `CodeEditorDiagnostic[] \| undefined`                          |         | External diagnostics, merged with syntax and schema ones                                                                                                                                                                                                                                                                                 |
| onDiagnosticsChange | `((diagnostics: CodeEditorDiagnostic[]) => void) \| undefined` |         | Fired when the merged diagnostics change                                                                                                                                                                                                                                                                                                 |
| apiRef              | `Ref<CodeEditorApi> \| undefined`                              |         | Imperative handle                                                                                                                                                                                                                                                                                                                        |
| cspNonce            | `string \| undefined`                                          |         | Nonce for the style tags the editor injects                                                                                                                                                                                                                                                                                              |
| onCopy              | `((value: string) => void) \| undefined`                       |         | Fired by `CodeSnippetCopyButton` after a copy, with the current document                                                                                                                                                                                                                                                                 |
| title               | `string \| undefined`                                          |         |                                                                                                                                                                                                                                                                                                                                          |
| defaultChecked      | `boolean \| undefined`                                         |         |                                                                                                                                                                                                                                                                                                                                          |
| autoFocus           | `boolean \| undefined`                                         |         |                                                                                                                                                                                                                                                                                                                                          |
| id                  | `string \| undefined`                                          |         |                                                                                                                                                                                                                                                                                                                                          |
| tabIndex            | `number \| undefined`                                          |         |                                                                                                                                                                                                                                                                                                                                          |
| rel                 | `string \| undefined`                                          |         |                                                                                                                                                                                                                                                                                                                                          |
| data-testid         | `string \| undefined`                                          |         |                                                                                                                                                                                                                                                                                                                                          |
| size                | `{ sm: string; md: string; lg: string; }`                      |         |                                                                                                                                                                                                                                                                                                                                          |

### CodeEditorContent

| Prop           | Type                   | Default | Description                                                                         |
| -------------- | ---------------------- | ------- | ----------------------------------------------------------------------------------- |
| lineNumbers    | `boolean \| undefined` | `false` | Show the line-number gutter (CodeSnippet's `<CodeSnippetLineNumbers />` equivalent) |
| title          | `string \| undefined`  |         |                                                                                     |
| defaultChecked | `boolean \| undefined` |         |                                                                                     |
| autoFocus      | `boolean \| undefined` |         |                                                                                     |
| id             | `string \| undefined`  |         |                                                                                     |
| tabIndex       | `number \| undefined`  |         |                                                                                     |
| rel            | `string \| undefined`  |         |                                                                                     |
| data-testid    | `string \| undefined`  |         |                                                                                     |

### CodeEditorRoot

| Prop                | Type                                                           | Default             | Description                                                                                                                                                                                                                                                                                                                              |
| ------------------- | -------------------------------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| value               | `string \| undefined`                                          |                     | Controlled document                                                                                                                                                                                                                                                                                                                      |
| defaultValue        | `string \| undefined`                                          |                     | Uncontrolled initial document (controlled vs uncontrolled is locked on first render)                                                                                                                                                                                                                                                     |
| onChange            | `((value: string) => void) \| undefined`                       |                     | Fired for user edits only, never for external `value` syncs                                                                                                                                                                                                                                                                              |
| language            | `CodeEditorLanguage \| undefined`                              | `text`              | Passed verbatim to the syntax adapter; selects the structural parser                                                                                                                                                                                                                                                                     |
| documentId          | `string \| undefined`                                          |                     | Document identity: changing it swaps to that document's cached state (undo history, selection, folds). Requires a controlled `value`: when the id changes, `value` must already hold that document's text (an uncached id starts from `value`). In uncontrolled mode the new document would be seeded with the previous document's text. |
| readOnly            | `boolean \| undefined`                                         | `false`             | Non-editable and unfocusable content; text remains selectable, searchable and copyable                                                                                                                                                                                                                                                   |
| startingLineNumber  | `number \| undefined`                                          | `1`                 | Offsets gutter numbers; `lines`, `folds`, `diagnostics` use absolute numbers                                                                                                                                                                                                                                                             |
| lines               | `Record<number, LineConfig> \| undefined`                      | `EMPTY_LINES`       | Per-line configuration keyed by absolute line number (static, see spec D6). Compared by identity: memoise it (or hoist it to a constant) — a new object every render reconfigures the lines and gutters on every render.                                                                                                                 |
| folds               | `CodeEditorFolds \| undefined`                                 |                     | Fold regions, or a function re-run after edits; collapsed state is kept by `id`. Compared by identity: memoise the array or pass a stable function (e.g. `getHttpFolds`) — an inline array/arrow reconfigures folds on every render and bypasses the re-run debounce.                                                                    |
| wrapLines           | `boolean \| undefined`                                         |                     | Controlled line wrapping                                                                                                                                                                                                                                                                                                                 |
| defaultWrapLines    | `boolean \| undefined`                                         | `false`             | Uncontrolled initial line wrapping                                                                                                                                                                                                                                                                                                       |
| onWrapLinesChange   | `((wrap: boolean) => void) \| undefined`                       |                     | Fired when the wrap button toggles wrapping                                                                                                                                                                                                                                                                                              |
| maxLines            | `number \| undefined`                                          | `0`                 | Height clamp in rows; auto-renders `CodeSnippetShowMoreButton` unless one is a direct child                                                                                                                                                                                                                                              |
| original            | `string \| undefined`                                          |                     | Enables diff mode against this text                                                                                                                                                                                                                                                                                                      |
| schema              | `JsonSchema \| undefined`                                      |                     | JSON Schema for the document (`json`) or the JSON body (`http`)                                                                                                                                                                                                                                                                          |
| completions         | `CodeEditorCompletionSource[] \| undefined`                    | `EMPTY_COMPLETIONS` | Consumer completion sources, added after the built-in ones. Compared by identity: a new array reconfigures autocomplete and closes an open list, so keep it stable (module constant or `useMemo`).                                                                                                                                       |
| diagnostics         | `CodeEditorDiagnostic[] \| undefined`                          | `EMPTY_DIAGNOSTICS` | External diagnostics, merged with syntax and schema ones                                                                                                                                                                                                                                                                                 |
| onDiagnosticsChange | `((diagnostics: CodeEditorDiagnostic[]) => void) \| undefined` |                     | Fired when the merged diagnostics change                                                                                                                                                                                                                                                                                                 |
| apiRef              | `Ref<CodeEditorApi> \| undefined`                              |                     | Imperative handle                                                                                                                                                                                                                                                                                                                        |
| cspNonce            | `string \| undefined`                                          |                     | Nonce for the style tags the editor injects                                                                                                                                                                                                                                                                                              |
| onCopy              | `((value: string) => void) \| undefined`                       |                     | Fired by `CodeSnippetCopyButton` after a copy, with the current document                                                                                                                                                                                                                                                                 |
| title               | `string \| undefined`                                          |                     |                                                                                                                                                                                                                                                                                                                                          |
| defaultChecked      | `boolean \| undefined`                                         |                     |                                                                                                                                                                                                                                                                                                                                          |
| autoFocus           | `boolean \| undefined`                                         |                     |                                                                                                                                                                                                                                                                                                                                          |
| id                  | `string \| undefined`                                          |                     |                                                                                                                                                                                                                                                                                                                                          |
| tabIndex            | `number \| undefined`                                          |                     |                                                                                                                                                                                                                                                                                                                                          |
| rel                 | `string \| undefined`                                          |                     |                                                                                                                                                                                                                                                                                                                                          |
| data-testid         | `string \| undefined`                                          |                     |                                                                                                                                                                                                                                                                                                                                          |
| size                | `{ sm: string; md: string; lg: string; }`                      | `sm`                |                                                                                                                                                                                                                                                                                                                                          |
