# @npm-questionpro/wick-ui-editor

A rich text editor component built with React and TipTap. Part of the Wick UI design system.

[![npm version](https://img.shields.io/npm/v/@npm-questionpro/wick-ui-editor.svg)](https://www.npmjs.com/package/@npm-questionpro/wick-ui-editor)
[![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)

## Installation

```bash
# npm
npm install @npm-questionpro/wick-ui-editor

# pnpm
pnpm add @npm-questionpro/wick-ui-editor

# yarn
yarn add @npm-questionpro/wick-ui-editor
```

Standalone — no dependency on `@npm-questionpro/wick-ui-lib` or `@npm-questionpro/wick-ui-icon`.

## Usage

```tsx
import {WuContentEditor} from '@npm-questionpro/wick-ui-editor'
import '@npm-questionpro/wick-ui-editor/dist/style.css'

function App() {
  return <WuContentEditor defaultValue="<p>Start typing...</p>" onUpdate={html => console.log(html)} />
}
```

### HTML Source Mode (opt-in)

Raw-HTML editing (with syntax highlighting via shiki and formatting via prettier) is a separate subpath so its weight
never reaches apps that don't use it. Both libraries are regular dependencies — nothing extra to install — and they are
loaded lazily at runtime, only when HTML Source Mode is opened.

```tsx
import {WuContentEditor} from '@npm-questionpro/wick-ui-editor'
import {WuHtmlSource} from '@npm-questionpro/wick-ui-editor/html'

function App() {
  return <WuContentEditor htmlSource={WuHtmlSource} />
}
```

Without `htmlSource`, the `html` toolbar button is hidden and `isHtml`/`setIsHtml` have no effect.

### Mentions

Pass a `mention` config to let users mention people by typing `@`. `items` receives the text typed after the trigger and
returns the options, synchronously or as a promise. It behaves like a search box: `items` runs on every keystroke, a
superseded request is aborted before its result can reach the list, and the previous results stay on screen until the
new ones arrive — so the list never blanks out or shows a spinner mid-typing.

```tsx
<WuContentEditor
  mention={{
    items: async ({query, signal}) => {
      const users = await searchUsers(query, {signal})
      return users.map(user => ({id: user.id, label: user.name}))
    },
  }}
/>
```

Pass the provided `signal` to `fetch` so a superseded request is actually cancelled rather than running to completion.
Ignoring it is safe — the result is discarded either way — but with one call per keystroke it is worth wiring up, and
caching inside `items` costs little.

The query keeps running across spaces, so `@Ross Gel` searches for `Ross Gel` rather than stopping at the space. It
stops after three words, so a stray `@` in prose does not query the rest of the paragraph. Matching is yours: compare
against a whitespace-stripped label if you want `@rossgeller` to match `Ross Geller` too.

A committed mention is stored as `<span data-type="mention" data-id="…" data-label="…">`, so `data-id` is what you
resolve against server-side. Pass `renderItem` to control the content of one row; keyboard navigation, selection and
ARIA stay owned by the editor.

Mentions already present in `defaultValue` render even when `mention` is omitted, so a display-only app does not need
the prop to show content another app authored.

### Theming

The editor styles itself with `--we-*` tokens that resolve to the host's `--wu-*` theme values (from wick-ui-lib) when
present, and fall back to built-in defaults otherwise. No setup needed either way.

## Props

| Prop                    | Type                      | Default     | Description                           |
| ----------------------- | ------------------------- | ----------- | ------------------------------------- |
| `defaultValue`          | `string`                  | `undefined` | Initial HTML content (auto-sanitized) |
| `onUpdate`              | `(html: string) => void`  | `undefined` | Fires when content changes            |
| `readonly`              | `boolean`                 | `false`     | Disable editing                       |
| `renderHtml`            | `boolean`                 | `false`     | Render raw HTML in read-only mode     |
| `toolbarPosition`       | `'top' \| 'bottom'`       | `'top'`     | Toolbar placement                     |
| `toolbarItems`          | `IToolbarExtensionKey[]`  | all         | Toolbar buttons to show               |
| `hideToolbar`           | `boolean`                 | `false`     | Hide the toolbar                      |
| `customFonts`           | `string[]`                | `undefined` | Extra font families in font picker    |
| `customToolbarChildren` | `ReactNode`               | `undefined` | Custom elements appended to toolbar   |
| `htmlSource`            | `ComponentType`           | `undefined` | HTML Source view (see above)          |
| `isHtml` / `setIsHtml`  | `boolean` / `(b) => void` | `undefined` | Controlled HTML Source Mode           |
| `mention`               | `IWuMentionConfig`        | `undefined` | Mention suggestions (see below)       |
| `className`             | `string`                  | `undefined` | Class on the root container           |

## Migrating from v1

### HTML source mode now requires `htmlSource`

`isHtml`/`setIsHtml` are no-ops without the new prop. Pass the source view explicitly:

```diff
+ import {WuHtmlSource} from '@npm-questionpro/wick-ui-editor/html'

  <WuContentEditor
+   htmlSource={WuHtmlSource}
    isHtml={isHtml}
    setIsHtml={setIsHtml}
  />
```

If you don't use HTML source mode, remove `isHtml` and `setIsHtml`.

---

## Documentation

Full documentation and interactive examples: [wick-ui.questionpro.com](https://wick-ui.questionpro.com)

## License

ISC © QuestionPro
