Skip to content
Open
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
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
---

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
---

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