Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/docs/api-reference/enriched-text-input.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,12 @@ interface OnChangeStateEvent {
mention: StyleState;
checkboxList: StyleState;
alignment: string;
customStyle: {
foregroundColor: string;
backgroundColor: string;
fontSize: number;
fontFamily: string;
};
}
```

Expand All @@ -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

Expand Down Expand Up @@ -973,6 +983,27 @@ 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. 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
- `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

### `.startMention()`

```ts
Expand Down
4 changes: 3 additions & 1 deletion docs/docs/fundamentals/core-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.

Expand Down
4 changes: 4 additions & 0 deletions docs/docs/fundamentals/html-format-and-supported-tags.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ Both show up in the [`onChangeState`](/fundamentals/core-concepts#the-style-stat
| Link | `<a>` | `<code>`, `<a>`, `<mention>` | `<codeblock>`, `<img>` |
| Mention | `<mention>` | `<code>`, `<a>` | `<codeblock>`, `<img>` |
| Image | `<img>` | `<a>`, `<mention>` | `<code>` |
| Custom style | `<span>` | -- | -- |

`<span>` carries the [custom style](/rich-text-formatting/custom-style) in its
`style` attribute - `color`, `background-color`, `font-size` and `font-family`.

:::note

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/rich-text-formatting/basic-styles.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

:::

Expand Down
98 changes: 98 additions & 0 deletions docs/docs/rich-text-formatting/custom-style.mdx
Original file line number Diff line number Diff line change
@@ -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 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.

:::

## 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: 'Font size',
isActive: !!state?.customStyle.fontSize,
onPress: () => ref.current?.setStyle({ fontSize: 26 }),
},
];
```

:::tip

Pass colors to `setStyle` as hex or named colors (e.g. `'red'`). They 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.

<InteractiveExample src={CustomStyleEditorSrc} component={CustomStyleEditor} />
2 changes: 1 addition & 1 deletion docs/docs/rich-text-formatting/inline-images.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 5
sidebar_position: 6
---

import InteractiveExample from '@site/src/components/InteractiveExample';
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/rich-text-formatting/links.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 2
sidebar_position: 3
---

import InteractiveExample from '@site/src/components/InteractiveExample';
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/rich-text-formatting/lists.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 4
sidebar_position: 5
---

import InteractiveExample from '@site/src/components/InteractiveExample';
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/rich-text-formatting/mentions.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 3
sidebar_position: 4
---

import InteractiveExample from '@site/src/components/InteractiveExample';
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/rich-text-formatting/text-alignment.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 6
sidebar_position: 7
---

import InteractiveExample from '@site/src/components/InteractiveExample';
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/rich-text-formatting/text-shortcuts.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 7
sidebar_position: 8
---

import InteractiveExample from '@site/src/components/InteractiveExample';
Expand Down
117 changes: 117 additions & 0 deletions docs/src/examples/CustomStyleEditor.tsx
Original file line number Diff line number Diff line change
@@ -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 = 'cursive';
Comment thread
hejsztynx marked this conversation as resolved.

export default function App() {
const ref = useRef<EnrichedTextInputInstance>(null);
const [state, setState] = useState<OnChangeStateEvent | null>(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: 'Font size',
isActive: !!customStyle?.fontSize,
onPress: () =>
ref.current?.setStyle({
fontSize: customStyle?.fontSize ? null : FONT_SIZE,
}),
},
{
label: 'Font family',
isActive: !!customStyle?.fontFamily,
onPress: () =>
ref.current?.setStyle({
fontFamily: customStyle?.fontFamily ? null : FONT_FAMILY,
}),
},
];

return (
<View style={styles.container}>
<EnrichedTextInput
ref={ref}
style={styles.input}
htmlStyle={htmlStyle}
placeholder="Select some text and style it below..."
onChangeState={e => setState(e.nativeEvent)}
/>
<View style={styles.row}>
{buttons.map(button => (
<Pressable
key={button.label}
style={[styles.button, button.isActive && styles.buttonActive]}
onPress={button.onPress}>
<Text style={[styles.text, button.isActive && styles.textActive]}>
{button.label}
</Text>
</Pressable>
))}
</View>
</View>
);
}

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: 120,
padding: 8,
borderRadius: 24,
borderWidth: 1,
borderColor: '#919fcf',
},
buttonActive: {
borderColor: '#57b495',
backgroundColor: '#57b495',
},
text: {
textAlign: 'center',
color: '#919fcf',
},
textActive: {
color: '#eef0ff',
},
});
Loading