Skip to content
Open
7 changes: 0 additions & 7 deletions docs/docs/rich-text-formatting/basic-styles.md

This file was deleted.

116 changes: 116 additions & 0 deletions docs/docs/rich-text-formatting/basic-styles.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
sidebar_position: 1
---

import InteractiveExample from '@site/src/components/InteractiveExample';
import BasicStylesEditor from '@site/src/examples/BasicStylesEditor';
import BasicStylesEditorSrc from '!!raw-loader!@site/src/examples/BasicStylesEditor';

# Basic styles
Comment thread
hejsztynx marked this conversation as resolved.

In [Your first editor](/fundamentals/your-first-editor) you wired up a single
**Bold** button. Every other formatting style works exactly the same way: a
`toggle` method on the `ref` changes the text, and `onChangeState` reports back
whether the style is active. The only exceptions to that rule are **mentions**,
**links** and **inline images**, which work a bit differently. They cannot be toggled
on/off on any text - they require additional data to be valid, but you'll learn more
about them in further sections. This page walks through the rest of the basic rich
Comment thread
hejsztynx marked this conversation as resolved.
text styles and the one core distinction that shapes how they behave - **inline**
versus **paragraph** formatting.

## Inline styles

Inline styles wrap a range of characters. Toggling one applies it to exactly the
characters you have selected, or - if nothing is selected - arms it so the next
characters you type come out styled. This is the family the bold belongs to:

- **Bold** - `toggleBold()`
- **Italic** - `toggleItalic()`
- **Underline** - `toggleUnderline()`
- **Strikethrough** - `toggleStrikeThrough()`
- **Inline code** - `toggleInlineCode()`

They combine freely, so a word can be bold and italic and underlined at
once.

:::note

There are two more inline styles, which you cannot toggle quite like the others - **links** and **mentions**. You'll learn about them in their respective sections, once you finish this chapter.

:::

## Paragraph styles

Paragraph styles apply to a whole paragraph at once. Toggling one affects the entire paragraph the
cursor sits in (or every paragraph the selection touches), no matter how much of
it is actually selected. The common ones are:

- **Headings** - `toggleH1()` through `toggleH6()`
- **Blockquote** - `toggleBlockQuote()`
- **Code block** - `toggleCodeBlock()`
- **Unordered list** - `toggleUnorderedList()`
- **Ordered list** - `toggleOrderedList()`
- **Checkbox list** - `toggleCheckboxList(checked: boolean)`

:::note

We will not touch list styles for now - we'll cover them in-depth in their [own section](/rich-text-formatting/lists).

:::

The key difference from inline styles is that **a paragraph can only have one
paragraph style at a time**. A line can't be both a heading and a blockquote, so
toggling a second paragraph style replaces the first rather than stacking on top.
The editor surfaces this through the `isConflicting` flag in `onChangeState`.

Code blocks go one step further: they **block** inline styles entirely. You
can't make text bold inside a code block, and `onChangeState` reports bold as
`isBlocking` there so you can grey the button out.

:::tip

The full table of what every style blocks or conflicts with is available in
[Supported tags](/fundamentals/html-format-and-supported-tags) and the `onChangeState` behavior is described in [Style state model](/fundamentals/core-concepts#the-style-state-model).

:::

## Building the toolbar

A toolbar with many styles is just the [first editor](/fundamentals/your-first-editor)
loop repeated. Each button reads its slice of the `onChangeState` payload for
`isActive` (to highlight it) and `isBlocking` (to disable it), and calls the
matching `toggle` method on press:

```tsx
const inlineButtons = [
{ label: 'Bold', state: state?.bold, onPress: () => ref.current?.toggleBold() },
{ label: 'Italic', state: state?.italic, onPress: () => ref.current?.toggleItalic() },
{ label: 'Underline', state: state?.underline, onPress: () => ref.current?.toggleUnderline() },
{ label: 'Strike', state: state?.strikeThrough, onPress: () => ref.current?.toggleStrikeThrough() },
];

const paragraphButtons = [
{ label: 'H1', state: state?.h1, onPress: () => ref.current?.toggleH1() },
{ label: 'H2', state: state?.h2, onPress: () => ref.current?.toggleH2() },
{ label: 'Quote', state: state?.blockQuote, onPress: () => ref.current?.toggleBlockQuote() },
{ label: 'Code', state: state?.codeBlock, onPress: () => ref.current?.toggleCodeBlock() },
];
```

## Try it out

And just like that we've expanded our editor with several new styles to choose
from a small toolbar. Select some text and toggle inline styles, or place the cursor
on a line and try the paragraph ones - notice how switching from **H1** to **Quote**
swaps the style instead of adding to it, and how the inline buttons grey out once
you're inside a **Code** block.

<InteractiveExample src={BasicStylesEditorSrc} component={BasicStylesEditor} />
Comment thread
hejsztynx marked this conversation as resolved.

:::info

This page is about _what_ each style does, using the built-in defaults. To change
how they look - heading sizes, blockquote borders, code block colors - see
[Styling the input](/core-functionalities/styling-the-input).

:::
7 changes: 0 additions & 7 deletions docs/docs/rich-text-formatting/inline-images.md

This file was deleted.

61 changes: 61 additions & 0 deletions docs/docs/rich-text-formatting/inline-images.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
sidebar_position: 5
---

import InteractiveExample from '@site/src/components/InteractiveExample';
import ImagesEditor from '@site/src/examples/ImagesEditor';
import ImagesEditorSrc from '!!raw-loader!@site/src/examples/ImagesEditor';

# Inline images

Aside from just styled text, the library offers much more. This includes inline images - pictures embedded straight into the text flow.

## Inserting an image

You can insert it explicitly with the `setImage` ref method:

```ts
setImage(src: string, width: number, height: number)
```

- `src` - an absolute file path or a remote image URL.
- `width` / `height` - the size the image should render at.

The image lands on its own line at the current selection, replacing any
selected text.

```tsx
const insertImage = () => {
ref.current?.setImage('https://picsum.photos/320/160', 160, 80);
};
```

:::note

You are responsible for the `width` and `height` you pass - the editor does not
measure the image for you, so if you want to preserve a picture's aspect ratio you
need to compute it yourself before calling `setImage`.

:::

## When the source can't load

If the `src` can't be resolved - a broken URL, a missing file - the editor doesn't
error out and renders a static placeholder in the image's place instead.

## Try it out

Place the cursor in the input and insert an image. The first button inserts a real
photo from [Lorem Picsum](https://picsum.photos) at a matching size; the second
points at a source that doesn't exist, so you get the fallback placeholder rather
than a crash.

<InteractiveExample src={ImagesEditorSrc} component={ImagesEditor} />

:::info

Images can also arrive by pasting rather than through `setImage` - the editor
surfaces those through the `onPasteImages` event, which hands you each image's URI,
MIME type, and dimensions so you can upload or resize before inserting.

:::
7 changes: 0 additions & 7 deletions docs/docs/rich-text-formatting/links.md

This file was deleted.

118 changes: 118 additions & 0 deletions docs/docs/rich-text-formatting/links.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
sidebar_position: 2
---

import InteractiveExample from '@site/src/components/InteractiveExample';
import LinksEditor from '@site/src/examples/LinksEditor';
import LinksEditorSrc from '!!raw-loader!@site/src/examples/LinksEditor';

# Links

A link is a piece of text with a URL attached to it. The editor creates links in
two ways - **automatically**, as the user types something that looks like a URL,
and **manually**, when you call `setLink` on a range of text. The two produce
links that behave differently, so it's worth understanding both.

## Automatic links

By default the editor watches what the user types and turns anything that looks
like a URL into a link on its own - no code required. These are **autolinks**.

Because an autolink is tied to the text that produced it, it stays a link only as
long as that text still matches. The moment the user edits the words, the pattern
no longer holds and the link naturally breaks.

### Customizing detection

You control what counts as a link with the
`linkRegex` prop. Pass your own `RegExp`
to recognize custom patterns - for example, only `https://` URLs or a custom
scheme that detects references like `issue-123`:

```tsx
// Detect "issue-123" style tokens as links.
const linkRegex = /issue-\d+/g;

<EnrichedTextInput linkRegex={linkRegex} /* ... */ />;
```

:::note

On iOS and Android the pattern is matched by the platform's native regex engine,
so not every JavaScript regex feature is available there - variable-width
lookbehinds, for instance, won't work. Pass `null` to turn automatic detection
off entirely.

:::

## Manual links

Manual links are applied explicitly with the
`setLink` ref method:

```ts
setLink(start: number, end: number, text: string, url: string)
```

It sets a link over the range from `start` to `end`, showing `text` and pointing
at `url`. Unlike an autolink, a manual link is a real, standalone style - the
user can keep editing its text and it stays a link.

:::tip

The three positional arguments line up exactly with the
`onChangeSelection` event payload, which reports
the current `start`, `end`, and the `text` it spans. So "link the current
selection" can be implemented quite simply:

:::

```tsx
const [selection, setSelection] = useState<OnChangeSelectionEvent | null>(null);

// ...
<EnrichedTextInput
onChangeSelection={e => setSelection(e.nativeEvent)}
/* ... */
/>;

const addLink = () => {
if (!selection) return;
ref.current?.setLink(
selection.start,
selection.end,
selection.text,
'https://swmansion.com',
);
};
```

:::tip

Passing a different `text` than what's selected replaces the selected text before
applying the link. To strip a link while keeping its text, call
`removeLink(start, end)`.

:::

## Try it out

The editor below uses a custom `linkRegex` that autolinks `issue-123` tokens - type them and watch it become a link. Then
select some words and hit **Link the selection** to attach a URL manually.
Notice the difference afterward: editing the manual link's text keeps it a link,
while editing an autolinked token breaks it.

<InteractiveExample src={LinksEditorSrc} component={LinksEditor} />

## Handling links with events

There is an event that allows you to handle user's interaction with links inside `EnrichedTextInput`.

- **`onLinkDetected`** fires when the cursor enters or leaves a created link.

:::info

This page covers creating and detecting links. To change how
they look, see [Styling the input](/core-functionalities/styling-the-input).

:::
7 changes: 0 additions & 7 deletions docs/docs/rich-text-formatting/lists.md

This file was deleted.

67 changes: 67 additions & 0 deletions docs/docs/rich-text-formatting/lists.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
sidebar_position: 4
---

import InteractiveExample from '@site/src/components/InteractiveExample';
import ListsEditor from '@site/src/examples/ListsEditor';
import ListsEditorSrc from '!!raw-loader!@site/src/examples/ListsEditor';

# Lists

Lists are [paragraph styles](/rich-text-formatting/basic-styles#paragraph-styles),
just like headings and blockquotes - toggling one affects the whole paragraph the
cursor sits in, or every paragraph the selection touches. The editor supports three
kinds:

- **Unordered list** - `toggleUnorderedList()`
- **Ordered list** - `toggleOrderedList()`
- **Checkbox list** - `toggleCheckboxList(checked: boolean)`

Because they're paragraph styles, **only one can be active on a paragraph at a
time** exactly as described in
[Basic styles](/rich-text-formatting/basic-styles#paragraph-styles).
Comment thread
hejsztynx marked this conversation as resolved.

:::note

The library does not support nested lists. You can only create a single-level list. If you try to insert a nested list in any way, e.g. by calling `setValue` on the editor's `ref`, the list will get flattened by the [normalizer](/fundamentals/core-concepts#normalization).

:::

## Bulleted and numbered lists

These two take no arguments - each call converts the affected paragraphs into a
list, and calling it again turns them back into plain paragraphs:

```tsx
ref.current?.toggleUnorderedList();
ref.current?.toggleOrderedList();
```

## Checkbox lists

This list's items need a starting state. `toggleCheckboxList` takes a `checked` boolean that
decides whether newly created boxes begin checked or unchecked:

```tsx
// New items start unchecked.
ref.current?.toggleCheckboxList(false);
```

Once the list exists, the user can tick individual items by tapping their
checkbox - you don't need to wire anything up for that.

## Try it out

Type a few lines, place the cursor on one (or select several), and toggle each
list type. Notice how switching from **Bulleted** to **Numbered** swaps the style
instead of stacking. Inserting a new line preserves the list format by creating another item, and tapping a checkbox toggles just that item.

<InteractiveExample src={ListsEditorSrc} component={ListsEditor} />
Comment thread
hejsztynx marked this conversation as resolved.

:::info

This page covers creating lists. To change how they look - bullet color and size,
marker weight, checkbox size, indentation - see
[Styling the input](/core-functionalities/styling-the-input).

:::
Loading
Loading