# @cdx-ui/theme-editor

![coverage](../../.github/badges/coverage-theme-editor.svg)

A guided, web-only editor for authoring Forge Design System `ThemeOverride` JSON. Financial institutions choose generative inputs and package-owned semantic selections through MUI form controls while a live panel previews real Forge UI components. The editor emits FI intent only; consuming apps persist it and apply it at runtime through `@cdx-ui/styles`.

> **Web-only.** This package depends on MUI (React DOM) and React Native Web. It is not available on native platforms.

## Installation

```bash
pnpm add @cdx-ui/theme-editor @cdx-ui/styles
```

`react`, `react-dom`, `react-native-web`, and `uniwind` are peer dependencies — apps already consuming `@cdx-ui/components` will have these in place. `@cdx-ui/components`, `@cdx-ui/icons`, and `@cdx-ui/styles` are resolved automatically.

## Usage

### 1. Add the editor to your Tailwind sources

The preview renders Forge UI components with Uniwind utility classes. Like the
other `@cdx-ui/*` packages, this package ships **no precompiled CSS or tokens** —
its `./styles.css` is a Tailwind v4 `@source` directive that adds the editor's
source to your content scan so the preview's utilities are generated. Import it
in your Tailwind entry stylesheet, alongside the rest of the design system:

```css
@import 'tailwindcss';
@import 'uniwind';

@import '@cdx-ui/styles/theme.css';
@import '@cdx-ui/styles/utilities.css';
@import '@cdx-ui/components/styles.css';
@import '@cdx-ui/theme-editor/styles.css';
```

### 2. Mount the editor

`onChange` fires (debounced, ~200ms) after the user edits a control with a valid target
`ThemeOverride`. It is not called on mount. Persist the payload from `onChange`; production
consumers apply it once during app startup:

```tsx
import { ThemeEditor } from '@cdx-ui/theme-editor';
import type { ThemeOverride } from '@cdx-ui/styles';

function ThemeConfigPage() {
  const handleChange = (override: ThemeOverride) => {
    void persistOverride(override); // your save (e.g. POST /api/themes)
  };

  return <ThemeEditor onChange={handleChange} showGetCode showThemeModeToggle />;
}
```

The editor parses and expands through `@cdx-ui/styles`, evaluates shared rules, and drives preview
through a stateful covering session. Clears and preset switches restore managed values without
dumping unrelated variables. Reload remains the recovery boundary after a physical writer failure.

### 3. Edit an existing override

Pass a previously saved override as `initialValue` to enter edit mode; the controls seed from its `inputs`:

```tsx
<ThemeEditor initialValue={savedOverride} onChange={handleChange} />
```

## Props

| Prop                  | Type                                | Default | Description                                                                                                                                                      |
| --------------------- | ----------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `onChange`            | `(override: ThemeOverride) => void` | —       | **Required.** Called (debounced, ~200ms) after the user edits a control with the current `ThemeOverride`. Not called on mount or while a color input is invalid. |
| `initialValue`        | `unknown`                           | —       | Parses a persisted value for edit mode and surfaces invalid or unsupported payload diagnostics.                                                                  |
| `showGetCode`         | `boolean`                           | `false` | Shows a "Get code" menu item that opens a popover with the override output and a copy-to-clipboard button.                                                       |
| `showThemeModeToggle` | `boolean`                           | `false` | Shows a light/dark toggle that switches the preview surface.                                                                                                     |

The `ThemeOverride` shape and runtime are documented in [`@cdx-ui/styles`](../styles/README.md).

## Behavior notes

- **Intent-only output.** Untouched preview defaults, derived Base, recipe members, and generated
  palettes are omitted. Incoming additive unknown fields are preserved.
- **Base.** Base derives from Brand until explicitly pinned; unpinning removes `basePrimary` intent.
- **Fonts.** Preset changes never silently substitute an incoming or touched invalid family.
- **Rules.** Approved structural/source-set errors block output. Provisional visual calibration
  remains warning-only.
- **Live preview.** The covering session writes the current sparse intent plus concrete fallback
  values needed to clear prior managed writes.
- **Preview isolation.** The preview is wrapped in `ScopedTheme` so the light/dark toggle only affects the preview surface, not the editor chrome.

## Package structure

```
theme-editor/
├── src/
│   ├── index.ts            # Public exports (ThemeEditor, ThemeEditorProps)
│   ├── styles.css          # `@source` directive — published as the ./styles.css export
│   ├── ThemeEditor.tsx     # Top-level component: state, debounced apply/emit, popover, toggle
│   ├── PreviewPanel.tsx    # Live preview surface (ScopedTheme + banking artboard)
│   ├── override/           # Draft provenance, serialization, controls, rules/runtime handoff
│   ├── validation.ts       # Hex validation
│   ├── fonts.ts            # Google Fonts loading for preset display fonts
│   ├── controls/           # PresetControl, FontControl, ColorControl
│   ├── components/         # ColorPickerInput, PresetPreview
│   └── previews/           # Forge UI sample cards rendered in the preview
├── global.css              # Tailwind/Uniwind entry for the build (not published)
├── vite.config.ts
└── package.json
```

## Building

```bash
pnpm --filter @cdx-ui/theme-editor build
```

Vite bundles `src/index.ts` to `dist/index.js` (externalizing React, React Native Web, Uniwind, and the `@cdx-ui/*` packages); `tsc` emits declarations. No CSS is emitted — `src/styles.css` (the `@source` directive) is published as-is and composes with the consumer's Tailwind build.

## Further reading

- [Override Structure](../../docs/internal/token-architecture/14-override-structure.md) — payload and responsibility contract
- [Theme Definition and Pipeline](../../docs/internal/token-architecture/02-theme-definition.md#build-time-vs-runtime-usage) — current build/runtime boundary
- [@cdx-ui/styles](../styles/README.md) — tokens, presets, and `applyThemeOverride`

## License

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)

MIT © 2026 Digital First Holdings LLC. See [LICENSE](./LICENSE) for details.
