Skip to content

Commit 9679241

Browse files
committed
docs: basic styles, mentions, links sections
1 parent dcc7034 commit 9679241

9 files changed

Lines changed: 575 additions & 21 deletions

File tree

docs/docs/rich-text-formatting/basic-styles.md

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
---
2+
sidebar_position: 1
3+
---
4+
5+
import InteractiveExample from '@site/src/components/InteractiveExample';
6+
import BasicStylesEditor from '@site/src/examples/BasicStylesEditor';
7+
import BasicStylesEditorSrc from '!!raw-loader!@site/src/examples/BasicStylesEditor';
8+
9+
# Basic styles
10+
11+
In [Your first editor](/fundamentals/your-first-editor) you wired up a single
12+
**Bold** button. Every other formatting style works exactly the same way: a
13+
`toggle` method on the `ref` changes the text, and `onChangeState` reports back
14+
whether the style is active. The only exceptions to that rule are **mentions**,
15+
**links** and **inline images**, which work a bit differently. They cannot be toggled
16+
on/off on any text - they require additional data to be valid,but you'll learn more
17+
about them in further sections. This page walks through the rest of the basic rich
18+
text styles and the one core distinction that shapes how they behave - **inline**
19+
versus **paragraph** formatting.
20+
21+
## Inline styles
22+
23+
Inline styles wrap a range of characters. Toggling one applies it to exactly the
24+
characters you have selected, or - if nothing is selected - arms it so the next
25+
characters you type come out styled. This is the family the bold belongs to:
26+
27+
- **Bold** - `toggleBold()`
28+
- **Italic** - `toggleItalic()`
29+
- **Underline** - `toggleUnderline()`
30+
- **Strikethrough** - `toggleStrikeThrough()`
31+
- **Inline code** - `toggleInlineCode()`
32+
33+
They combine freely, so a word can be bold and italic and underlined at
34+
once.
35+
36+
:::note
37+
38+
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.
39+
40+
:::
41+
42+
## Paragraph styles
43+
44+
Paragraph styles apply to a whole paragraph at once. Toggling one affects the entire paragraph the
45+
cursor sits in (or every paragraph the selection touches), no matter how much of
46+
it is actually selected. The common ones are:
47+
48+
- **Headings** - `toggleH1()` through `toggleH6()`
49+
- **Blockquote** - `toggleBlockQuote()`
50+
- **Code block** - `toggleCodeBlock()`
51+
- **Unordered list** - `toggleUnorderedList()`
52+
- **Ordered list** - `toggleOrderedList()`
53+
- **Checkbox list** - `toggleCheckboxList(checked: boolean)`
54+
55+
:::note
56+
57+
We will not touch list styles for now - we'll cover them in-depth in their [own section](/rich-text-formatting/lists).
58+
59+
:::
60+
61+
The key difference from inline styles is that **a paragraph can only have one
62+
paragraph style at a time**. A line can't be both a heading and a blockquote, so
63+
toggling a second paragraph style replaces the first rather than stacking on top.
64+
The editor surfaces this through the `isConflicting` flag in `onChangeState`.
65+
66+
Code blocks go one step further: they **block** inline styles entirely. You
67+
can't make text bold inside a code block, and `onChangeState` reports bold as
68+
`isBlocking` there so you can grey the button out.
69+
70+
:::tip
71+
72+
The full table of what every style blocks or conflicts with is available in
73+
[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).
74+
75+
:::
76+
77+
## Building the toolbar
78+
79+
A toolbar with many styles is just the [first editor](/fundamentals/your-first-editor)
80+
loop repeated. Each button reads its slice of the `onChangeState` payload for
81+
`isActive` (to highlight it) and `isBlocking` (to disable it), and calls the
82+
matching `toggle` method on press:
83+
84+
```tsx
85+
const inlineButtons = [
86+
{ label: 'Bold', state: state?.bold, onPress: () => ref.current?.toggleBold() },
87+
{ label: 'Italic', state: state?.italic, onPress: () => ref.current?.toggleItalic() },
88+
{ label: 'Underline', state: state?.underline, onPress: () => ref.current?.toggleUnderline() },
89+
{ label: 'Strike', state: state?.strikeThrough, onPress: () => ref.current?.toggleStrikeThrough() },
90+
];
91+
92+
const paragraphButtons = [
93+
{ label: 'H1', state: state?.h1, onPress: () => ref.current?.toggleH1() },
94+
{ label: 'H2', state: state?.h2, onPress: () => ref.current?.toggleH2() },
95+
{ label: 'Quote', state: state?.blockQuote, onPress: () => ref.current?.toggleBlockQuote() },
96+
{ label: 'Code', state: state?.codeBlock, onPress: () => ref.current?.toggleCodeBlock() },
97+
];
98+
```
99+
100+
## Try it out
101+
102+
And just like that we've expanded our editor with several new styles to choose
103+
from a small toolbar. Select some text and toggle inline styles, or place the cursor
104+
on a line and try the paragraph ones - notice how switching from **H1** to **Quote**
105+
swaps the style instead of adding to it, and how the inline buttons grey out once
106+
you're inside a **Code** block.
107+
108+
<InteractiveExample src={BasicStylesEditorSrc} component={BasicStylesEditor} />
109+
110+
:::info
111+
112+
This page is about _what_ each style does, using the built-in defaults. To change
113+
how they look - heading sizes, blockquote borders, code block colors - see
114+
[Styling the input](/core-functionalities/styling-the-input).
115+
116+
:::

docs/docs/rich-text-formatting/links.md

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
---
2+
sidebar_position: 2
3+
---
4+
5+
import InteractiveExample from '@site/src/components/InteractiveExample';
6+
import LinksEditor from '@site/src/examples/LinksEditor';
7+
import LinksEditorSrc from '!!raw-loader!@site/src/examples/LinksEditor';
8+
9+
# Links
10+
11+
A link is a piece of text with a URL attached to it. The editor creates links in
12+
two ways - **automatically**, as the user types something that looks like a URL,
13+
and **manually**, when you call `setLink` on a range of text. The two produce
14+
links that behave differently, so it's worth understanding both.
15+
16+
## Automatic links
17+
18+
By default the editor watches what the user types and turns anything that looks
19+
like a URL into a link on its own - no code required. These are **autolinks**.
20+
21+
Because an autolink is tied to the text that produced it, it stays a link only as
22+
long as that text still matches. The moment the user edits the words, the pattern
23+
no longer holds and the link naturally breaks.
24+
25+
### Customizing detection
26+
27+
You control what counts as a link with the
28+
`linkRegex` prop. Pass your own `RegExp`
29+
to recognize custom patterns - for example, only `https://` URLs or a custom
30+
scheme that detects references like `issue-123`:
31+
32+
```tsx
33+
// Detect "issue-123" style tokens as links.
34+
const linkRegex = /issue-\d+/g;
35+
36+
<EnrichedTextInput linkRegex={linkRegex} /* ... */ />;
37+
```
38+
39+
:::note
40+
41+
On iOS and Android the pattern is matched by the platform's native regex engine,
42+
so not every JavaScript regex feature is available there - variable-width
43+
lookbehinds, for instance, won't work. Pass `null` to turn automatic detection
44+
off entirely.
45+
46+
:::
47+
48+
## Manual links
49+
50+
Manual links are applied explicitly with the
51+
`setLink` ref method:
52+
53+
```ts
54+
setLink(start: number, end: number, text: string, url: string)
55+
```
56+
57+
It sets a link over the range from `start` to `end`, showing `text` and pointing
58+
at `url`. Unlike an autolink, a manual link is a real, standalone style - the
59+
user can keep editing its text and it stays a link.
60+
61+
:::tip
62+
63+
The three positional arguments line up exactly with the
64+
`onChangeSelection` event payload, which reports
65+
the current `start`, `end`, and the `text` it spans. So "link the current
66+
selection" can be implemented quite simply:
67+
68+
:::
69+
70+
```tsx
71+
const [selection, setSelection] = useState<OnChangeSelectionEvent | null>(null);
72+
73+
// ...
74+
<EnrichedTextInput
75+
onChangeSelection={e => setSelection(e.nativeEvent)}
76+
/* ... */
77+
/>;
78+
79+
const addLink = () => {
80+
if (!selection) return;
81+
ref.current?.setLink(
82+
selection.start,
83+
selection.end,
84+
selection.text,
85+
'https://swmansion.com',
86+
);
87+
};
88+
```
89+
90+
:::tip
91+
92+
Passing a different `text` than what's selected replaces the selected text before
93+
applying the link. To strip a link while keeping its text, call
94+
`removeLink(start, end)`.
95+
96+
:::
97+
98+
## Try it out
99+
100+
The editor below uses a custom `linkRegex` that autolinks `issue-123` tokens on
101+
top of the usual URL detection - type either and watch it become a link. Then
102+
select some words and hit **Link the selection** to attach a URL manually.
103+
Notice the difference afterward: editing the manual link's text keeps it a link,
104+
while editing an autolinked token breaks it.
105+
106+
<InteractiveExample src={LinksEditorSrc} component={LinksEditor} />
107+
108+
:::info
109+
110+
This page covers creating and detecting links. To react to links the user
111+
touches, listen for the
112+
`onLinkDetected` event, and to change how
113+
links look, see [Styling the input](/core-functionalities/styling-the-input).
114+
115+
:::

docs/docs/rich-text-formatting/mentions.md

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
sidebar_position: 3
3+
---
4+
5+
import InteractiveExample from '@site/src/components/InteractiveExample';
6+
import MentionEditor from '@site/src/examples/MentionEditor';
7+
import MentionEditorSrc from '!!raw-loader!@site/src/examples/MentionEditor';
8+
9+
# Mentions
10+
11+
This is a powerful feature allowing for a customizable inline style for a "mentioning" phrase - `@someone`,
12+
`#some-channel`, or whatever pattern fits your app. Each mention is able to carry any custom data you attach to it, so it's more than
13+
styled text: it's a reference you can act on later.
14+
15+
## Mention indicators
16+
17+
A mention begins with an **indicator** - a single character that tells the editor
18+
"a mention starts here". The set of recognized indicators is controlled by the
19+
`mentionIndicators` prop, which defaults to
20+
`['@']`:
21+
22+
```tsx
23+
// Recognize both @user and #channel mentions.
24+
<EnrichedTextInput mentionIndicators={['@', '#']} /* ... */ />;
25+
```
26+
27+
Typing one of these characters starts a mention. You can also start one
28+
programmatically with `startMention(indicator)`.
29+
30+
## Inserting a mention with `setMention`
31+
32+
Once a mention is being edited - the user has typed an indicator, or you've
33+
called `startMention` - you insert the finished mention with `setMention`:
34+
35+
```ts
36+
setMention(
37+
indicator: string,
38+
text: string,
39+
attributes?: Record<string, string>,
40+
)
41+
```
42+
43+
`setMention` replaces whatever the user has typed including the indicator with
44+
`text`. It also stores any `attributes` you pass - this is useful if you want to associate a mention with some data. Those attributes are preserved through the HTML, so they
45+
survive a round-trip through `getHTML` and `setValue`.
46+
47+
```tsx
48+
const insertMention = (user: { id: string; name: string }) => {
49+
ref.current?.setMention('@', `@${user.name}`, { id: user.id });
50+
};
51+
```
52+
53+
:::note
54+
55+
`setMention` is only operational when an actual mention preceded by an indicator is being edited. Otherwise it does nothing.
56+
57+
:::
58+
59+
## Try it out
60+
61+
Type `@`, then tap the button to turn it
62+
into a completed `@mention`. Notice how you can type a part of a mention, e.g. `@Jo`, move the cursor inside its range and the mention insertion will properly replace it. The result is a single styled unit - try deleting one of its characters and see how it's removed.
63+
64+
<InteractiveExample src={MentionEditorSrc} component={MentionEditor} />
65+
66+
:::note
67+
68+
A mention query spans up to two words - the editor keeps tracking the mention after one
69+
space, and a second space ends it. That makes `@John Doe` a valid mention.
70+
71+
:::
72+
73+
## Driving mentions with events
74+
75+
This example is deliberately bare-bones - one hard-coded user behind a button.
76+
A real app watches what the user types after the
77+
indicator and shows a picker. Three events give you everything you need for
78+
that:
79+
80+
- **`onStartMention`** fires when a mention starts - a good moment to open your suggestion list.
81+
- **`onChangeMention`** fires on every edit to the query, handing you the text
82+
typed after the indicator - use it to filter the list.
83+
- **`onEndMention`** fires when the mention stops being edited - the cursor
84+
moved away, or the query was closed - so you can dismiss the list.
85+
86+
You'd open the list on `onStartMention`, filter it on `onChangeMention`, and
87+
call `setMention` when the user picks someone. This example doesn't wire up any
88+
of that; the full picker flow is covered end-to-end in the
89+
[Mention-only input](/guides/mention-only-input) guide.
90+
91+
:::tip
92+
93+
Prefix your custom attribute names with `data-` (e.g. `{ 'data-profile-url': user.profileUrl }`).
94+
Non-standard attributes can be dropped when the HTML passes through a sanitizer -
95+
this library's, or one on your own backend - whereas `data-*` attributes are the HTML standard for persisting custom data and are kept.
96+
97+
:::

0 commit comments

Comments
 (0)