-
Notifications
You must be signed in to change notification settings - Fork 57
docs: Rich text formatting section #713
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
hejsztynx
wants to merge
10
commits into
@ksienkiewicz/docs-fundamentals
Choose a base branch
from
@ksienkiewicz/docs-rich-text-formatting
base: @ksienkiewicz/docs-fundamentals
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
9679241
docs: basic styles, mentions, links sections
hejsztynx 55e8fd0
docs: lists, imgs, alignment, shortcuts sections
hejsztynx 2aaaf9b
docs: update links section
hejsztynx 5fd037c
Apply suggestions from code review
hejsztynx 756b988
Merge branch '@ksienkiewicz/docs-fundamentals' into @ksienkiewicz/doc…
hejsztynx 942497f
refactor: basic styles editor types clarity
hejsztynx e0f944c
Update docs/docs/rich-text-formatting/lists.mdx
hejsztynx da510a4
docs: links and mention onDeteced events
hejsztynx 27ce5d5
Merge branch '@ksienkiewicz/docs-fundamentals' into @ksienkiewicz/doc…
hejsztynx c892da8
Merge branch '@ksienkiewicz/docs-fundamentals' into @ksienkiewicz/doc…
hejsztynx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
|
||
| 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 | ||
|
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} /> | ||
|
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). | ||
|
|
||
| ::: | ||
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. | ||
|
|
||
| ::: |
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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). | ||
|
|
||
| ::: |
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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). | ||
|
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} /> | ||
|
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). | ||
|
|
||
| ::: | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.