From c90e0cce99b7cf613c18f7f2ccfeb5cf88de98ff Mon Sep 17 00:00:00 2001 From: Krystian Sienkiewicz Date: Fri, 16 Oct 2026 01:53:08 +0200 Subject: [PATCH 1/6] docs: custom style --- .../docs/api-reference/enriched-text-input.md | 38 ++++++ docs/docs/fundamentals/core-concepts.md | 4 +- .../html-format-and-supported-tags.md | 4 + .../rich-text-formatting/basic-styles.mdx | 2 +- .../rich-text-formatting/custom-style.mdx | 98 +++++++++++++++ .../rich-text-formatting/inline-images.mdx | 2 +- docs/docs/rich-text-formatting/links.mdx | 2 +- docs/docs/rich-text-formatting/lists.mdx | 2 +- docs/docs/rich-text-formatting/mentions.mdx | 2 +- .../rich-text-formatting/text-alignment.mdx | 2 +- .../rich-text-formatting/text-shortcuts.mdx | 2 +- docs/src/examples/CustomStyleEditor.tsx | 117 ++++++++++++++++++ 12 files changed, 267 insertions(+), 8 deletions(-) create mode 100644 docs/docs/rich-text-formatting/custom-style.mdx create mode 100644 docs/src/examples/CustomStyleEditor.tsx diff --git a/docs/docs/api-reference/enriched-text-input.md b/docs/docs/api-reference/enriched-text-input.md index 382e60a39..8f2e9488b 100644 --- a/docs/docs/api-reference/enriched-text-input.md +++ b/docs/docs/api-reference/enriched-text-input.md @@ -347,6 +347,12 @@ interface OnChangeStateEvent { mention: StyleState; checkboxList: StyleState; alignment: string; + customStyle: { + foregroundColor: string; + backgroundColor: string; + fontSize: number; + fontFamily: string; + }; } ``` @@ -356,6 +362,10 @@ interface OnChangeStateEvent { - `isConflicting` - toggling the style removes a conflicting active style - `alignment` - current text alignment of the paragraph at the cursor: `'left'`, `'center'`, `'right'`, `'justify'`, or `'auto'` +- `customStyle` - the [custom style](/rich-text-formatting/custom-style) values + within the current selection. A field that isn't set, or + differs across the selection, is empty - `''` for strings and `0` for + `fontSize`. Colors are reported as hex strings (e.g. `#FF0000`) :::note @@ -973,6 +983,34 @@ up with natural alignment, the same as `'auto'`. ::: +### `.setStyle()` + +```ts +setStyle: (customStyle: { + foregroundColor?: ColorValue | null; + backgroundColor?: ColorValue | null; + fontSize?: number | null; + fontFamily?: string | null; +}) => void; +``` + +Sets the [custom style](/rich-text-formatting/custom-style) at the current +selection. Passing `null` for a field removes it. + +- `foregroundColor` - text color. Takes a + [color](https://reactnative.dev/docs/colors) value +- `backgroundColor` - text highlight color. Takes a + [color](https://reactnative.dev/docs/colors) value +- `fontSize` - font size of the text +- `fontFamily` - font family of the text + +:::note + +Platform colors created with `PlatformColor` or `DynamicColorIOS` are not +supported - passing one behaves the same as passing `null`. + +::: + ### `.startMention()` ```ts diff --git a/docs/docs/fundamentals/core-concepts.md b/docs/docs/fundamentals/core-concepts.md index 52fd5593b..e391df2b9 100644 --- a/docs/docs/fundamentals/core-concepts.md +++ b/docs/docs/fundamentals/core-concepts.md @@ -71,7 +71,7 @@ The HTML format that both components expect is identical, what allows you to int ## The style state model -Not every style can be combined with every other. For example, a paragraph can't be both a heading and a list item, code blocks don't support inline formatting such as bold or italic. The editor tracks this and reports it through `onChangeState`, which gives each style three booleans: +Not every style can be combined with every other. For example, a paragraph can't be both a heading and a list item, code blocks don't support inline formatting such as bold or italic. The editor tracks this and reports it through `onChangeState`, which gives the majority of styles three booleans: - **`isActive`** - whether the style is applied at the current selection. Use it to highlight a toolbar button. @@ -82,6 +82,8 @@ Not every style can be combined with every other. For example, a paragraph can't 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. +Only [text alignment](/rich-text-formatting/text-alignment) and [custom style](/rich-text-formatting/custom-style) do not expose those three booleans in the `onChangeState` event, as they can be applied on any existing style. Instead, they simply carry their current value. To see the exact shape of the event's payload, see the `onChangeState` [API reference](/api-reference/enriched-text-input#onchangestate). + Driving your toolbar from these three flags keeps the UI honest: buttons light up and grey out according to the editor's state. diff --git a/docs/docs/fundamentals/html-format-and-supported-tags.md b/docs/docs/fundamentals/html-format-and-supported-tags.md index 962dae581..ce2488e26 100644 --- a/docs/docs/fundamentals/html-format-and-supported-tags.md +++ b/docs/docs/fundamentals/html-format-and-supported-tags.md @@ -32,6 +32,10 @@ Both show up in the [`onChangeState`](/fundamentals/core-concepts#the-style-stat | Link | `` | ``, ``, `` | ``, `` | | Mention | `` | ``, `` | ``, `` | | Image | `` | ``, `` | `` | +| Custom style | `` | -- | -- | + +`` carries the [custom style](/rich-text-formatting/custom-style) in its +`style` attribute - `color`, `background-color`, `font-size` and `font-family`. :::note diff --git a/docs/docs/rich-text-formatting/basic-styles.mdx b/docs/docs/rich-text-formatting/basic-styles.mdx index ce52600e2..41e9b8395 100644 --- a/docs/docs/rich-text-formatting/basic-styles.mdx +++ b/docs/docs/rich-text-formatting/basic-styles.mdx @@ -35,7 +35,7 @@ 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. +There are a few more inline styles, which you cannot toggle quite like the others - **custom style**, **links** and **mentions**. You'll learn about them in their respective sections, once you finish this chapter. ::: diff --git a/docs/docs/rich-text-formatting/custom-style.mdx b/docs/docs/rich-text-formatting/custom-style.mdx new file mode 100644 index 000000000..5e52c8266 --- /dev/null +++ b/docs/docs/rich-text-formatting/custom-style.mdx @@ -0,0 +1,98 @@ +--- +sidebar_position: 2 +--- + +import InteractiveExample from '@site/src/components/InteractiveExample'; +import CustomStyleEditor from '@site/src/examples/CustomStyleEditor'; +import CustomStyleEditorSrc from '!!raw-loader!@site/src/examples/CustomStyleEditor'; + +# Custom style + +Aside from the predefined [basic styles](/rich-text-formatting/basic-styles) +you can apply a **custom style**, which consists of four pieces of formatting: +text color, highlight, font size and font family. Similarly as the style-toggling +methods, it applies to the current input's selection. + +## Setting a custom style + +`setStyle` accepts an object with four optional fields: + +```tsx +setStyle( + { + foregroundColor?: string, + backgroundColor?: string, + fontSize?: number, + fontFamily?: string, + } +); +``` + +Each call respects the already present custom style and modifies it with whatever +fields you pass. For example, if you want to remove a single field, pass `null`: + +```tsx +// Remove the highlight, keep the color, size and font as they are. +ref.current?.setStyle({ backgroundColor: null }); +``` + +Custom style doesn't conflict with or block any other style, so it combines +freely with both inline styles and paragraph styles. + +:::note + +If a current inline style conflicts with what **custom style** sets, the inline style wins. +E.g. if you try to apply `backgroundColor` or `fontFamily` to text with an inline `code` style, +which has that formatting defined on its own, you will not see any difference. + +::: + +## Reading the current style + +Since custom style carries values rather than a simple on/off flag, `onChangeState` +reports it as a separate `customStyle` object instead of the usual +`isActive` / `isBlocking` / `isConflicting` +[booleans](/fundamentals/core-concepts#the-style-state-model): + +```ts +customStyle: { + foregroundColor: string; + backgroundColor: string; + fontSize: number; + fontFamily: string; +} +``` + +Each field holds the value at the current selection. When a field isn't set, it comes back +empty: `''` for strings and `0` for `fontSize`. That makes it easy to check whether a field +is applied: + +```tsx +const customStyleButtons = [ + { + label: 'Color', + isActive: !!state?.customStyle.foregroundColor, + onPress: () => ref.current?.setStyle({ foregroundColor: '#e5484d' }), + }, + { + label: 'Size', + isActive: !!state?.customStyle.fontSize, + onPress: () => ref.current?.setStyle({ fontSize: 26 }), + }, +]; +``` + +:::tip + +Colors are always reported as uppercase hex strings - `#RRGGBB`, or `#RRGGBBAA`. + +::: + +## Try it out + +Each button below sets one field of the custom style on the selection, and +removes it when the field is already applied. Select a word and give it a color, +then a highlight - notice how the second call doesn't touch the first one. Try +mixing them with different fonts and sizes. + + diff --git a/docs/docs/rich-text-formatting/inline-images.mdx b/docs/docs/rich-text-formatting/inline-images.mdx index 4677ad594..a88b448a5 100644 --- a/docs/docs/rich-text-formatting/inline-images.mdx +++ b/docs/docs/rich-text-formatting/inline-images.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 6 --- import InteractiveExample from '@site/src/components/InteractiveExample'; diff --git a/docs/docs/rich-text-formatting/links.mdx b/docs/docs/rich-text-formatting/links.mdx index 556ace638..7df5a5070 100644 --- a/docs/docs/rich-text-formatting/links.mdx +++ b/docs/docs/rich-text-formatting/links.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 3 --- import InteractiveExample from '@site/src/components/InteractiveExample'; diff --git a/docs/docs/rich-text-formatting/lists.mdx b/docs/docs/rich-text-formatting/lists.mdx index 118cf1d57..c8beb3338 100644 --- a/docs/docs/rich-text-formatting/lists.mdx +++ b/docs/docs/rich-text-formatting/lists.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 5 --- import InteractiveExample from '@site/src/components/InteractiveExample'; diff --git a/docs/docs/rich-text-formatting/mentions.mdx b/docs/docs/rich-text-formatting/mentions.mdx index 147a1d1e8..17142018d 100644 --- a/docs/docs/rich-text-formatting/mentions.mdx +++ b/docs/docs/rich-text-formatting/mentions.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 4 --- import InteractiveExample from '@site/src/components/InteractiveExample'; diff --git a/docs/docs/rich-text-formatting/text-alignment.mdx b/docs/docs/rich-text-formatting/text-alignment.mdx index 9bca7329e..9dc27f4cd 100644 --- a/docs/docs/rich-text-formatting/text-alignment.mdx +++ b/docs/docs/rich-text-formatting/text-alignment.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 6 +sidebar_position: 7 --- import InteractiveExample from '@site/src/components/InteractiveExample'; diff --git a/docs/docs/rich-text-formatting/text-shortcuts.mdx b/docs/docs/rich-text-formatting/text-shortcuts.mdx index 4a5e19938..551b36a73 100644 --- a/docs/docs/rich-text-formatting/text-shortcuts.mdx +++ b/docs/docs/rich-text-formatting/text-shortcuts.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 7 +sidebar_position: 8 --- import InteractiveExample from '@site/src/components/InteractiveExample'; diff --git a/docs/src/examples/CustomStyleEditor.tsx b/docs/src/examples/CustomStyleEditor.tsx new file mode 100644 index 000000000..d1fc62eff --- /dev/null +++ b/docs/src/examples/CustomStyleEditor.tsx @@ -0,0 +1,117 @@ +import { EnrichedTextInput } from 'react-native-enriched-html'; +import type { + EnrichedTextInputInstance, + OnChangeStateEvent, +} from 'react-native-enriched-html'; +import { useRef, useState } from 'react'; +import { View, StyleSheet, Pressable, Text } from 'react-native'; +import { htmlStyle } from './htmlStyle'; + +const TEXT_COLOR = '#ff8560'; +const HIGHLIGHT_COLOR = '#c1c6e5'; +const FONT_SIZE = 26; +const FONT_FAMILY = 'DM Mono'; + +export default function App() { + const ref = useRef(null); + const [state, setState] = useState(null); + + const customStyle = state?.customStyle; + + const buttons = [ + { + label: 'Color', + isActive: !!customStyle?.foregroundColor, + onPress: () => + ref.current?.setStyle({ + foregroundColor: customStyle?.foregroundColor ? null : TEXT_COLOR, + }), + }, + { + label: 'Highlight', + isActive: !!customStyle?.backgroundColor, + onPress: () => + ref.current?.setStyle({ + backgroundColor: customStyle?.backgroundColor + ? null + : HIGHLIGHT_COLOR, + }), + }, + { + label: 'Size', + isActive: !!customStyle?.fontSize, + onPress: () => + ref.current?.setStyle({ + fontSize: customStyle?.fontSize ? null : FONT_SIZE, + }), + }, + { + label: 'Font', + isActive: !!customStyle?.fontFamily, + onPress: () => + ref.current?.setStyle({ + fontFamily: customStyle?.fontFamily ? null : FONT_FAMILY, + }), + }, + ]; + + return ( + + setState(e.nativeEvent)} + /> + + {buttons.map(button => ( + + + {button.label} + + + ))} + + + ); +} + +const styles = StyleSheet.create({ + container: { gap: 12 }, + input: { + fontSize: 18, + color: '#232736', + padding: 12, + borderRadius: 12, + minHeight: 96, + backgroundColor: '#eef0ff', + }, + row: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'center', + gap: 8, + }, + button: { + width: 90, + padding: 8, + borderRadius: 24, + borderWidth: 1, + borderColor: '#919fcf', + }, + buttonActive: { + borderColor: '#57b495', + backgroundColor: '#57b495', + }, + text: { + textAlign: 'center', + color: '#919fcf', + }, + textActive: { + color: '#eef0ff', + }, +}); From cc13c698c99ba03a0660a741f9e6f1f16d10e188 Mon Sep 17 00:00:00 2001 From: Krystian Sienkiewicz Date: Fri, 16 Oct 2026 02:00:10 +0200 Subject: [PATCH 2/6] fix: font change to something more conspicuous --- docs/src/examples/CustomStyleEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/examples/CustomStyleEditor.tsx b/docs/src/examples/CustomStyleEditor.tsx index d1fc62eff..3f890f8b0 100644 --- a/docs/src/examples/CustomStyleEditor.tsx +++ b/docs/src/examples/CustomStyleEditor.tsx @@ -10,7 +10,7 @@ import { htmlStyle } from './htmlStyle'; const TEXT_COLOR = '#ff8560'; const HIGHLIGHT_COLOR = '#c1c6e5'; const FONT_SIZE = 26; -const FONT_FAMILY = 'DM Mono'; +const FONT_FAMILY = 'cursive'; export default function App() { const ref = useRef(null); From dfbaaf452bb5070443697eba8d425e01f3e13589 Mon Sep 17 00:00:00 2001 From: Krystian Sienkiewicz Date: Fri, 16 Oct 2026 09:55:29 +0200 Subject: [PATCH 3/6] docs: tweaks regarding colors --- docs/docs/api-reference/enriched-text-input.md | 15 ++++----------- docs/docs/rich-text-formatting/custom-style.mdx | 3 ++- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/docs/docs/api-reference/enriched-text-input.md b/docs/docs/api-reference/enriched-text-input.md index 8f2e9488b..20c3d3022 100644 --- a/docs/docs/api-reference/enriched-text-input.md +++ b/docs/docs/api-reference/enriched-text-input.md @@ -997,20 +997,13 @@ setStyle: (customStyle: { Sets the [custom style](/rich-text-formatting/custom-style) at the current selection. Passing `null` for a field removes it. -- `foregroundColor` - text color. Takes a - [color](https://reactnative.dev/docs/colors) value -- `backgroundColor` - text highlight color. Takes a - [color](https://reactnative.dev/docs/colors) value +- `foregroundColor` - text color; use a hex (e.g. `#FFE066`) or named (e.g. + `'red'`) color +- `backgroundColor` - text highlight color; use a hex (e.g. `#FFE066`) or named + (e.g. `'red'`) color - `fontSize` - font size of the text - `fontFamily` - font family of the text -:::note - -Platform colors created with `PlatformColor` or `DynamicColorIOS` are not -supported - passing one behaves the same as passing `null`. - -::: - ### `.startMention()` ```ts diff --git a/docs/docs/rich-text-formatting/custom-style.mdx b/docs/docs/rich-text-formatting/custom-style.mdx index 5e52c8266..9803e3807 100644 --- a/docs/docs/rich-text-formatting/custom-style.mdx +++ b/docs/docs/rich-text-formatting/custom-style.mdx @@ -84,7 +84,8 @@ const customStyleButtons = [ :::tip -Colors are always reported as uppercase hex strings - `#RRGGBB`, or `#RRGGBBAA`. +Pass colors to `setStyle` as hex or named colors (e.g. `'red'`). They are +always reported as uppercase hex strings - `#RRGGBB`, or `#RRGGBBAA`. ::: From 22568b438d19a48a8991d76448388c899e8bd799 Mon Sep 17 00:00:00 2001 From: Krystian Sienkiewicz <146986839+hejsztynx@users.noreply.github.com> Date: Fri, 16 Oct 2026 10:20:42 +0200 Subject: [PATCH 4/6] refactor: adjusted formatting button labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Igor FurgaƂa <74370735+exploIF@users.noreply.github.com> --- docs/docs/rich-text-formatting/custom-style.mdx | 2 +- docs/src/examples/CustomStyleEditor.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/rich-text-formatting/custom-style.mdx b/docs/docs/rich-text-formatting/custom-style.mdx index 9803e3807..b944fd456 100644 --- a/docs/docs/rich-text-formatting/custom-style.mdx +++ b/docs/docs/rich-text-formatting/custom-style.mdx @@ -75,7 +75,7 @@ const customStyleButtons = [ onPress: () => ref.current?.setStyle({ foregroundColor: '#e5484d' }), }, { - label: 'Size', + label: 'Font size', isActive: !!state?.customStyle.fontSize, onPress: () => ref.current?.setStyle({ fontSize: 26 }), }, diff --git a/docs/src/examples/CustomStyleEditor.tsx b/docs/src/examples/CustomStyleEditor.tsx index 3f890f8b0..73eaed73e 100644 --- a/docs/src/examples/CustomStyleEditor.tsx +++ b/docs/src/examples/CustomStyleEditor.tsx @@ -38,7 +38,7 @@ export default function App() { }), }, { - label: 'Size', + label: 'Font size', isActive: !!customStyle?.fontSize, onPress: () => ref.current?.setStyle({ @@ -46,7 +46,7 @@ export default function App() { }), }, { - label: 'Font', + label: 'Font family', isActive: !!customStyle?.fontFamily, onPress: () => ref.current?.setStyle({ From 401ca03fc650a631ee109a23daebb1aa1afa548e Mon Sep 17 00:00:00 2001 From: Krystian Sienkiewicz Date: Fri, 16 Oct 2026 10:22:30 +0200 Subject: [PATCH 5/6] feat: bigger formatting example buttons --- docs/src/examples/CustomStyleEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/examples/CustomStyleEditor.tsx b/docs/src/examples/CustomStyleEditor.tsx index 73eaed73e..c52e5fc09 100644 --- a/docs/src/examples/CustomStyleEditor.tsx +++ b/docs/src/examples/CustomStyleEditor.tsx @@ -97,7 +97,7 @@ const styles = StyleSheet.create({ gap: 8, }, button: { - width: 90, + width: 120, padding: 8, borderRadius: 24, borderWidth: 1, From 8e7df80231790088505046438ec44fbd94d6f07a Mon Sep 17 00:00:00 2001 From: Krystian Sienkiewicz Date: Tue, 20 Oct 2026 17:23:36 +0200 Subject: [PATCH 6/6] docs: review adjustments --- docs/docs/api-reference/enriched-text-input.md | 2 +- docs/docs/rich-text-formatting/custom-style.mdx | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/docs/api-reference/enriched-text-input.md b/docs/docs/api-reference/enriched-text-input.md index 20c3d3022..eb4339d43 100644 --- a/docs/docs/api-reference/enriched-text-input.md +++ b/docs/docs/api-reference/enriched-text-input.md @@ -995,7 +995,7 @@ setStyle: (customStyle: { ``` Sets the [custom style](/rich-text-formatting/custom-style) at the current -selection. Passing `null` for a field removes it. +selection. Omitting a field leaves the given formatting untouched, whereas passing `null` for a field removes it. - `foregroundColor` - text color; use a hex (e.g. `#FFE066`) or named (e.g. `'red'`) color diff --git a/docs/docs/rich-text-formatting/custom-style.mdx b/docs/docs/rich-text-formatting/custom-style.mdx index b944fd456..4c3789f25 100644 --- a/docs/docs/rich-text-formatting/custom-style.mdx +++ b/docs/docs/rich-text-formatting/custom-style.mdx @@ -41,9 +41,8 @@ freely with both inline styles and paragraph styles. :::note -If a current inline style conflicts with what **custom style** sets, the inline style wins. -E.g. if you try to apply `backgroundColor` or `fontFamily` to text with an inline `code` style, -which has that formatting defined on its own, you will not see any difference. +If a current style modifies the same thing that **custom style** sets, we have the following priority: +**paragraph style** < **custom style** < **inline style**. E.g. if you try to apply `foregroundColor` custom style to a text with a link (which is an inline style), the link's foreground color stays. :::