From f44bda64efd72c7b4c032fffd17ac8ded176cb52 Mon Sep 17 00:00:00 2001 From: Krystian Sienkiewicz Date: Thu, 16 Jul 2026 12:22:03 +0200 Subject: [PATCH 01/11] docs: fundamentals --- docs/docs/fundamentals/core-concepts.md | 86 +- docs/docs/fundamentals/getting-started.md | 129 +- .../html-format-and-supported-tags.md | 74 +- docs/docs/fundamentals/your-first-editor.md | 7 - docs/docs/fundamentals/your-first-editor.mdx | 142 + docs/docusaurus.config.js | 16 + docs/package.json | 8 +- .../components/InteractiveExample/index.tsx | 66 + .../InteractiveExample/styles.module.css | 70 + docs/src/examples/FirstEditor.tsx | 38 + docs/src/theme/MDXComponents.js | 2 + docs/yarn.lock | 2672 ++++++++++++++++- 12 files changed, 3192 insertions(+), 118 deletions(-) delete mode 100644 docs/docs/fundamentals/your-first-editor.md create mode 100644 docs/docs/fundamentals/your-first-editor.mdx create mode 100644 docs/src/components/InteractiveExample/index.tsx create mode 100644 docs/src/components/InteractiveExample/styles.module.css create mode 100644 docs/src/examples/FirstEditor.tsx diff --git a/docs/docs/fundamentals/core-concepts.md b/docs/docs/fundamentals/core-concepts.md index 22319d745..5826f73f0 100644 --- a/docs/docs/fundamentals/core-concepts.md +++ b/docs/docs/fundamentals/core-concepts.md @@ -4,4 +4,88 @@ sidebar_position: 3 # Core concepts - +You've built an editor. Before going on, it's worth understanding the few +ideas the whole library is built on. They explain why the API looks the way it +does and help you understand further chapters better. + +## The input is uncontrolled + +`EnrichedTextInput` does not take its content from a prop and does not push +every keystroke back into React state. It owns its content on the native side +and you talk to it through a `ref`. + +This is deliberate. Rich text changes constantly — every character, selection +move and style toggle — and round-tripping all of that through JavaScript state +would be extremely slow and open to a possible de-synchronization of those states. +Keeping it native makes the editor fast and stable. + +In practice this means: + +- To **change** content or styling, call a method: `ref.current?.toggleBold()`, + `ref.current?.setValue(html)`, `ref.current?.setLink(...)`. +- To **observe** content or styling, listen to an event: `onChangeState`, + `onChangeHtml`, `onChangeSelection`. + +You never set a `value` prop and re-render to make an edit happen. + +## HTML is the source of truth + +The editor's content is HTML. `setValue` and `defaultValue` seeds it with an HTML string, +`getHTML` reads the current content back, and `onChangeHtml` streams it as it +changes. What you store and what you render is a string of HTML. + +The library uses a fixed set of standard and custom tags, so the output is +predictable and portable. [Supported HTML tags](/fundamentals/html-format-and-supported-tags) +lists exactly what it produces and accepts. + +:::caution + +You own sanitization. The library doesn't guarantee safe or clean HTML, so +sanitize anything you persist, render elsewhere, or accept from untrusted +sources. + +::: + +## Normalization + +## Normalization + +HTML can often be messy. If a user pastes text from Google Docs or MS Word, it arrives packed with wrapper tags, inline styles, and structural quirks that don't match the format we expect. + +To handle this, both components provide `useHtmlNormalizer` prop that normalizes any incoming HTML. The normalizer cleans and restructures the input into the predictable format the library expects. For example, it maps `` to ``, safely unwraps `
` containers into `

` tags, and strips tags that are unsupported. The `useHtmlNormalizer` prop defaults to `true`. + +All supported and canonical tags are listed in [Supported HTML tags](/fundamentals/html-format-and-supported-tags). + +## Two components, one styling API + +The library is split into an editor and a viewer: + +- **`EnrichedTextInput`** — the interactive editor from the previous page. +- **`EnrichedText`** — a read-only display component that renders the input's + HTML. + +Both accept the same `htmlStyle` prop, which describes how each tag looks +(heading sizes, blockquote borders, code block colors, and so on). Because they +share it, text looks identical whether it's being edited or displayed — no +drift between the two. A common setup edits in `EnrichedTextInput`, stores the +`getHTML` output, and later shows it with `EnrichedText`. + +## The style state model + +Not every style can combine with every other. A heading isn't a list; bold +inside a code block doesn't make sense. The editor tracks this and reports it +through `onChangeState`, which gives each style three booleans: + +- **`isActive`** — the style is applied at the current selection. Use it to + highlight a toolbar button. +- **`isBlocking`** — another active style forbids this one entirely, so toggling + it would do nothing. For example bold is blocked inside a code block. Use it + to disable a button. +- **`isConflicting`** — this style would replace an active one if toggled on. + For example switching a blockquote paragraph to a heading removes the + blockquote. Use it to hint that the toggle is a swap, not an addition. + +Driving your toolbar from these three flags keeps the UI honest: buttons light +up and grey out according do the editor's state. +To find the comprehensive list about what style blocks or conflicts with, check out +[Supported HTML tags](/fundamentals/html-format-and-supported-tags). diff --git a/docs/docs/fundamentals/getting-started.md b/docs/docs/fundamentals/getting-started.md index 505177606..abc2cee6d 100644 --- a/docs/docs/fundamentals/getting-started.md +++ b/docs/docs/fundamentals/getting-started.md @@ -3,6 +3,133 @@ slug: / sidebar_position: 1 --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + # Getting started - +The goal of the _Fundamentals_ section is to get you from an empty project to a +working rich text editor and to give you the mental model you need to build on +your own. Let's dive in. + +## What is React Native Enriched HTML? + +React Native Enriched HTML is a rich text solution for React Native built by +[Software Mansion](https://swmansion.com/). + +It ships two fully native components: `EnrichedTextInput`, a rich text editor +that styles text live as you type, and `EnrichedText`, a read-only display component that +renders the input's output. Both speak HTML — the input +produces it, the display consumes it. + +Not only does this library allow you to apply basic rich text styles you know well, +like *bold* or _italic_, but also provides more powerful tools like `mentions`, something +you will learn more about in the next chapters. + +iOS and Android are and Web are all supported. iOS and Android are both stable, where Web is still experimental, so expect +its behavior to change between releases. + +## Prerequisites + +The library works only with the +[React Native New Architecture (Fabric)](https://reactnative.dev/architecture/landing-page). +It's enabled by default on recent React Native versions; if your app still runs +the old architecture you'll need to switch before installing. + +For the exact React Native versions we support, see +[Compatibility](/misc/compatibility). + +## Installation + +### Bare React Native + +Install the package: + + + + ```bash + npm install react-native-enriched-html + ``` + + + + ```bash + yarn add react-native-enriched-html + ``` + + + +The library contains native code, so rebuild your app after installing. On iOS, +install the pods first: + +```sh +cd ios && bundler install && bundler exec pod install +``` + +### Expo + +Install with the Expo CLI so the correct version is picked for your SDK: + +```sh +npx expo install react-native-enriched-html +``` + +Then regenerate the native projects: + +```sh +npx expo prebuild +``` + +:::note + +The library needs native code, so it won't run in Expo Go. Use a +[development build](https://docs.expo.dev/develop/development-builds/introduction/) +instead. + +::: + +### Web + +Install the package: + + + + ```bash + npm install react-native-enriched-html + ``` + + + + ```bash + yarn add react-native-enriched-html + ``` + + + +The shipped components share the React Native's API, minus the functionalities a +browser can't provide. See the _Web support_ guide for what's covered. + +## Nightly builds + +To try features before they land in a stable release, install the nightly +build: + + + + ```bash + npm install react-native-enriched-html@nightly + ``` + + + + ```bash + yarn add react-native-enriched-html@nightly + ``` + + + +Nightlies are published to npm automatically and may contain breaking changes. + +--- + +You're set up. Next, let's [build your first editor](/fundamentals/your-first-editor). diff --git a/docs/docs/fundamentals/html-format-and-supported-tags.md b/docs/docs/fundamentals/html-format-and-supported-tags.md index 7cda41d42..9a5e02bfe 100644 --- a/docs/docs/fundamentals/html-format-and-supported-tags.md +++ b/docs/docs/fundamentals/html-format-and-supported-tags.md @@ -4,4 +4,76 @@ sidebar_position: 4 # HTML format and supported tags - +The editor works with a fixed set of standard and custom HTML tags — it both +produces them in its output and accepts them as input. This page is the +reference for that set. + +Styles fall into two groups: **inline** tags that wrap a range of characters, +and **paragraph** tags that apply to whole lines. Not all of them combine +freely, and there are two kinds of restriction: + +- **Conflicting** — toggling a style that conflicts with an active one replaces + it. Toggling `

` on a `
` paragraph removes the blockquote and + applies the heading. +- **Blocking** — a blocked style can't be toggled at all while the blocking + style is active. `` is blocked inside ``, so bold can't be + applied there. + +Both show up in the [`onChangeState`](/fundamentals/core-concepts) payload as +`isConflicting` and `isBlocking`. + +## Inline tags + +| Style | HTML tag | Conflicts with | Blocked by | +| ------------- | ----------- | ---------------------------- | ---------------------- | +| Bold | `` | -- | `` | +| Italic | `` | -- | `` | +| Underline | `` | -- | `` | +| Strikethrough | `` | -- | `` | +| Inline code | `` | ``, `` | ``, `` | +| Link | `` | ``, ``, `` | ``, `` | +| Mention | `` | ``, `` | ``, `` | +| Image | `` | ``, `` | `` | + +:::note + +Headings also block bold when `bold: true` is set on the heading in the +`htmlStyle` prop. The heading already renders as bold, so toggling bold on top +of it is redundant and therefore blocked. + +::: + +## Paragraph tags + +Only one paragraph-level style can be active per paragraph — they all conflict +with each other. + +Some paragraph styles are containers that wrap each line inside them with an +**inner content tag**. Each line of a `

+ {tab === Tab.PREVIEW && ( + + )} + + + {tab === Tab.PREVIEW ? ( +
+ Loading…
}> + {() => } + + + ) : ( +
+ {src.trim()} +
+ )} + + ); +} diff --git a/docs/src/components/InteractiveExample/styles.module.css b/docs/src/components/InteractiveExample/styles.module.css new file mode 100644 index 000000000..e74f5db7b --- /dev/null +++ b/docs/src/components/InteractiveExample/styles.module.css @@ -0,0 +1,70 @@ +.container { + display: flex; + flex-direction: column; + contain: content; + background-color: var(--swm-off-background); + border: 1px solid var(--swm-border); + border-radius: var(--ifm-global-radius); + margin-bottom: var(--ifm-leading); + overflow: hidden; +} + +.toolbar { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.5em 1.25em; + border-bottom: 1px solid var(--swm-border); +} + +.tabs { + display: flex; + gap: 1.5em; +} + +.tab { + padding: 0 0 2px 0; + border: none; + border-bottom: 1px solid transparent; + background-color: transparent; + color: var(--swm-interactive-button-color); + cursor: pointer; + font-family: var(--swm-body-font); + font-weight: 500; + font-size: 16px; +} + +.tabActive { + color: var(--swm-interactive-button-active); + border-bottom-color: var(--swm-interactive-button-active); +} + +.reset { + padding: 2px 12px; + border: 1px solid var(--swm-border); + border-radius: 20px; + background-color: transparent; + color: var(--swm-interactive-button-color); + cursor: pointer; + font-family: var(--swm-body-font); + font-weight: 500; + font-size: 14px; +} + +.reset:hover { + color: var(--swm-interactive-button-active); +} + +.preview { + padding: 1.5em; +} + +.loading { + color: var(--swm-interactive-button-color); +} + +.code [class*='codeBlockContainer'] { + box-shadow: none; + margin-bottom: 0; + border-radius: 0; +} diff --git a/docs/src/examples/FirstEditor.tsx b/docs/src/examples/FirstEditor.tsx new file mode 100644 index 000000000..c6220ef4e --- /dev/null +++ b/docs/src/examples/FirstEditor.tsx @@ -0,0 +1,38 @@ +import { EnrichedTextInput } from 'react-native-enriched-html'; +import type { + EnrichedTextInputInstance, + OnChangeStateEvent, +} from 'react-native-enriched-html'; +import { useRef, useState } from 'react'; +import { View, Button, StyleSheet } from 'react-native'; + +export default function App() { + const ref = useRef(null); + const [state, setState] = useState(null); + + return ( + + setState(e.nativeEvent)} + /> +