Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
7 changes: 0 additions & 7 deletions docs/docs/guides/chat-input-with-images.md

This file was deleted.

133 changes: 132 additions & 1 deletion docs/docs/guides/custom-context-menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,135 @@ sidebar_position: 4

# Custom context menu

<!-- TODO: write content for this page -->
The `contextMenuItems` prop lets you add your own actions to the native
text-selection menu - the popover that e.g. shows **Copy / Paste / Cut** when the
user long-presses selected text.

:::info

This is a **native-only** feature (iOS and Android) - which
is why this page has no live preview. To see it in action, run the snippet below in the
[example app](https://github.com/software-mansion/react-native-enriched-html/tree/main/apps/example).

:::

## The shape of an item

```ts
interface ContextMenuItem {
text: string; // the label shown in the menu
visible?: boolean; // whether to show it (defaults to true)
onPress: (args: {
text: string; // the currently selected text
selection: { start: number; end: number }; // its range
styleState: OnChangeStateEvent; // active styles
}) => void;
}
Comment thread
hejsztynx marked this conversation as resolved.
```

Every `onPress` receives the same three-field payload, resolved at the moment
the item is tapped:

- **`text`** - the selected text.
- **`selection`** - the `start` and `end` offsets of the selection.
- **`styleState`** - the latest style state, the same object you get from
`onChangeState`.

`visible` is read when the menu opens, so you can drive it from state to show an
item only in the right context.

## Example

This editor adds three items. The first two read the selection payload and run an
editor command. The third links the selection, so it's only shown via `visible`
when there's actually a ranged selection to link:

```tsx
import { EnrichedTextInput } from 'react-native-enriched-html';
import type {
ContextMenuItem,
EnrichedTextInputInstance,
OnChangeSelectionEvent,
} from 'react-native-enriched-html';
import { useMemo, useRef, useState } from 'react';
import { View, StyleSheet, Alert } from 'react-native';

export default function App() {
const ref = useRef<EnrichedTextInputInstance>(null);
const [selection, setSelection] = useState<OnChangeSelectionEvent | null>(
null
);

const hasRangedSelection = !!selection && selection.start !== selection.end;

const contextMenuItems: ContextMenuItem[] = useMemo(
() => [
{
// `text` and `selection` describe what the user long-pressed.
text: 'Show selection',
onPress: ({ text, selection: range }) => {
Alert.alert(
'Selection',
`"${text}" at [${range.start}, ${range.end}]`
);
},
},
{
// Menu items can call any editor command through the ref.
text: 'Bold',
onPress: () => {
ref.current?.toggleBold();
},
},
{
// Only useful with a ranged selection, so hide it otherwise; when
// shown, `selection` lets you target the exact range you were given.
text: 'Link to Software Mansion',
visible: hasRangedSelection,
onPress: ({ text, selection: range }) => {
ref.current?.setLink(
range.start,
range.end,
text,
'https://swmansion.com'
);
},
},
],
[hasRangedSelection]
);

return (
<View style={styles.container}>
<EnrichedTextInput
ref={ref}
style={styles.input}
placeholder="Select some text, then long-press it..."
contextMenuItems={contextMenuItems}
onChangeSelection={(e) => setSelection(e.nativeEvent)}
/>
</View>
);
}

const styles = StyleSheet.create({
container: { gap: 12 },
input: {
fontSize: 18,
color: '#232736',
padding: 12,
borderRadius: 12,
minHeight: 96,
backgroundColor: '#eef0ff',
},
});
```

:::note

Item placement differs per platform. On **iOS** your items appear in array
order, before the system items (Copy/Paste/Cut). On **Android** there is no
guaranteed order, and depending on the device manufacturer your items may be
tucked into an overflow submenu.

:::
7 changes: 0 additions & 7 deletions docs/docs/guides/emojis.md

This file was deleted.

85 changes: 85 additions & 0 deletions docs/docs/guides/emojis.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
sidebar_position: 3
Comment thread
hejsztynx marked this conversation as resolved.
Outdated
---

import InteractiveExample from '@site/src/components/InteractiveExample';
import EmojiEditor from '@site/src/examples/EmojiEditor';
import EmojiEditorSrc from '!!raw-loader!@site/src/examples/EmojiEditor';

# Emojis

Mentions are powerful enough that you can build much more with them. An emoji picker is just a mention where the display text does not match the query. We'll use `:` as the indicator: the user types `:smile`, picks from a list, and the shortcode is
replaced with the emoji itself. If the mention events are new to you, start with
[Mentions](/rich-text-formatting/mentions) and the
[User and channel mentions](/guides/user-and-channel-mentions) guide; this page reuses the
same flow.

## A shortcode table

Map each shortcode to the glyph it inserts:

```tsx
const EMOJIS = [
{ shortcode: 'smile', char: '😄' },
{ shortcode: 'heart', char: '❤️' },
{ shortcode: 'fire', char: '🔥' },
{ shortcode: 'rocket', char: '🚀' },
];
```

## Wiring it up

Register `:` as the only indicator and filter the table with the text typed
after it. Because people habitually close the shortcode (`:smile:`), strip a
trailing colon before matching:

```tsx
<EnrichedTextInput
mentionIndicators={[':']}
onStartMention={() => setOpen(true)}
onChangeMention={({ text }) => setQuery(text)}
onEndMention={() => setOpen(false)}
// ...
/>;

const q = query.replace(/:$/, '').toLowerCase();
const suggestions = EMOJIS.filter(e => e.shortcode.startsWith(q));
```

When the user picks, `setMention` inserts the glyph as the mention's display
text. The `data-shortcode` makes a handy attribute if you ever need to reconstruct it:

```tsx
const pick = (emoji) => {
ref.current?.setMention(':', emoji.char, {
'data-shortcode': emoji.shortcode,
});
};
```

Since the emoji glyph is the whole mention, drop the usual highlight so it
reads as plain text:

```tsx
const htmlStyle = {
mention: {
':': {
color: '#232736',
backgroundColor: 'transparent',
textDecorationLine: 'none',
},
},
};
```

## Try it out

Type `:` followed by a name - `:fire`, `:heart` - then tap a suggestion. The shortcode is replaced with a single emoji that you can place anywhere in the text.

<InteractiveExample src={EmojiEditorSrc} component={EmojiEditor} />

:::info

If you want to see the whole code used to build this example, you can find it by switching the tab from `Preview` to `Code`.

:::
7 changes: 0 additions & 7 deletions docs/docs/guides/mention-only-input.md

This file was deleted.

112 changes: 112 additions & 0 deletions docs/docs/guides/user-and-channel-mentions.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
sidebar_position: 2
Comment thread
hejsztynx marked this conversation as resolved.
Outdated
---

import InteractiveExample from '@site/src/components/InteractiveExample';
import MentionOnlyEditor from '@site/src/examples/MentionOnlyEditor';
import MentionOnlyEditorSrc from '!!raw-loader!@site/src/examples/MentionOnlyEditor';

# User and channel mentions

This guide wires the mention events into a complete picker - the flow behind a
chat composer where typing `@` suggests people and `#` suggests channels. If you
haven't met the mention API yet, read
[Mentions](/rich-text-formatting/mentions) first; here we move quickly and
assume the events and `setMention` are familiar.

## The data behind a mention

Mentions are particularly useful if they point at something. Keep two lists around - one
for users, one for channels - each item carrying an `id` you can attach
to the finished mention:

```tsx
const USERS = [
{ id: 'u1', name: 'John Doe' },
{ id: 'u2', name: 'Jane Smith' },
{ id: 'u3', name: 'Alice Johnson' },
{ id: 'u4', name: 'Bob Brown' },
];

const CHANNELS = [
{ id: 'c1', name: 'general' },
{ id: 'c2', name: 'engineering' },
{ id: 'c3', name: 'random' },
{ id: 'c4', name: 'announcements' },
];
```

## Wiring the picker

Now let's register both indicators and the events callbacks:

```tsx
<EnrichedTextInput
mentionIndicators={['@', '#']}
onStartMention={openPicker} // fired when '@' or '#' is typed
onChangeMention={updateQuery} // fired on every keystroke after it
onEndMention={closePicker} // fired when the mention is left
// ...
/>
```

`onStartMention` hands you the indicator, so you know whether to show people or
channels. `onChangeMention` hands you the `text` typed so far - filter your list
with it. `onEndMention` fires when the cursor leaves the mention, so you dismiss
the list.

When the user taps a suggestion, finish the mention with `setMention`. Pass the
same indicator that started it, the display text, and
the item's data as attributes:

```tsx
const pick = (item) => {
ref.current?.setMention(indicator, `${indicator}${item.name}`, {
id: item.id,
});
};
```

Now let's give each indicator its own look through
`htmlStyle.mention`:

```tsx
const htmlStyle = {
mention: {
'@': { color: '#2b7a4b', backgroundColor: '#d8f3e3' },
'#': { color: '#2b5f9e', backgroundColor: '#d8e6f9' },
},
};
```

## Try it out

Type `@` to filter people or `#` to filter channels, keep typing to narrow the
list, then tap a suggestion.

<InteractiveExample src={MentionOnlyEditorSrc} component={MentionOnlyEditor} />
Comment thread
hejsztynx marked this conversation as resolved.

:::info

If you want to see the whole code used to build this example, you can find it by switching the tab from `Preview` to `Code`.

:::

:::tip

A mention is only active while the editor is focused - if it blurs,
`onEndMention` fires and `setMention` becomes a no-op. On native, tapping a
suggestion never steals focus, so it just works. On web it does, so the rows
call `preventDefault` on `mousedown` to keep the editor focused. The example
wraps that in a small `keepEditorFocused` helper (see the Code tab).

:::

:::note

The attributes you pass to `setMention` (here `{ id }`) ride along in the HTML
and survive a round-trip through `getHTML` / `setValue`. Prefix custom keys with
`data-` if they need to outlive a sanitizer - see the note in
[Mentions](/rich-text-formatting/mentions).

:::
2 changes: 1 addition & 1 deletion docs/docs/rich-text-formatting/mentions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ that:
You'd open the list on `onStartMention`, filter it on `onChangeMention`, and
call `setMention` when the user picks someone. This example doesn't wire up any
of that; the full picker flow is covered end-to-end in the
[Mention-only input](/guides/mention-only-input) guide.
[user and channel mentions](/guides/user-and-channel-mentions) guide.

:::tip

Expand Down
Loading
Loading