Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f5ece32
feat(emojis): add built-in emoji dataset and search index
oliverlaz Jul 2, 2026
27eab23
feat(emojis): render a native React emoji picker panel
oliverlaz Jul 2, 2026
7d672bc
feat(emojis): style the native emoji picker panel
oliverlaz Jul 2, 2026
9362f63
feat(emojis): add in-picker search and empty state
oliverlaz Jul 2, 2026
9a30e5b
feat(emojis): add skin-tone selector and frequently-used section
oliverlaz Jul 2, 2026
3fd1f0d
perf(emojis): virtualize the emoji grid by category
oliverlaz Jul 2, 2026
61f0ac9
feat(emojis): keyboard navigation and accessibility for the picker
oliverlaz Jul 2, 2026
1ae3205
feat(emojis): add emoji picker i18n strings for all 12 locales
oliverlaz Jul 2, 2026
334f6fb
feat(emojis): remove emoji-mart peer dependencies
oliverlaz Jul 2, 2026
2236157
Merge remote-tracking branch 'origin/master' into feat/replace-emoji-…
oliverlaz Jul 2, 2026
e61c8c5
fix(emojis): persist skin tone and frequently-used across picker open…
oliverlaz Jul 2, 2026
ae560f8
fix(emojis): stop silently ignoring unsupported EmojiPicker pickerProps
oliverlaz Jul 2, 2026
d856bd8
docs(emojis): import emoji-picker.css where the picker is used
oliverlaz Jul 3, 2026
05dba1a
fix(emojis): make EmojiPicker theme='light' override a dark ancestor
oliverlaz Jul 3, 2026
ce7ea1a
fix(emojis): recover from failed dataset loads and drop invalid grid …
oliverlaz Jul 3, 2026
ce86bdc
fix(emojis): match the built-in picker's styling to emoji-mart
oliverlaz Jul 3, 2026
37a14c5
Merge remote-tracking branch 'origin/master' into feat/replace-emoji-…
oliverlaz Jul 3, 2026
a20dd46
fix(emojis): remove doubled search-field padding so the icon aligns
oliverlaz Jul 3, 2026
737a03a
feat(emojis): show emoji shortcode in preview and keep frequently-use…
oliverlaz Jul 3, 2026
044e97a
feat(emojis): restore reacting with any emoji via loadDefaultExtended…
oliverlaz Jul 3, 2026
79b2a23
fix(emojis): let keyboard navigation cross virtualized category bound…
oliverlaz Jul 3, 2026
c375e7d
fix(emojis): correct three picker option/state bugs
oliverlaz Jul 3, 2026
97b285a
perf(emojis): drive picker keyboard navigation from the category model
oliverlaz Jul 3, 2026
e539909
fix(emojis): mark the last category active when scrolled to the bottom
oliverlaz Jul 3, 2026
98ba7ff
perf(emojis): debounce picker search and share one search index
oliverlaz Jul 3, 2026
8376d53
fix(emojis): improve picker keyboard accessibility
oliverlaz Jul 3, 2026
0e4025b
feat(emojis): support curated emoji-mart pickerProps options
oliverlaz Jul 6, 2026
bf7575b
feat(emojis): add StreamEmojiPicker and deprecate emoji-mart EmojiPicker
oliverlaz Jul 7, 2026
5ddc4bd
fix(emojis): keep the vite example emoji preview pinned to the right
oliverlaz Jul 7, 2026
9055a79
chore: sync i18n strings
oliverlaz Jul 7, 2026
5adf3af
Merge origin/master into feat/replace-emoji-mart (resolve i18n confli…
oliverlaz Jul 7, 2026
d162162
refactor(emojis): slim down the StreamEmojiPicker public API
oliverlaz Jul 13, 2026
5912c64
feat(emojis): add StreamEmojiPicker compound components + useEmojiPic…
oliverlaz Jul 13, 2026
c46e939
docs(emojis): document StreamEmojiPicker composition + add a composed…
oliverlaz Jul 13, 2026
fdce557
fix(examples): scroll the composed emoji grid instead of overflowing
oliverlaz Jul 13, 2026
11f2c57
refactor(emojis): drop componentName args from context hook calls
oliverlaz Jul 13, 2026
9f29cfb
test(emojis): consolidate picker slot tests into integration coverage
oliverlaz Jul 13, 2026
eeb8f6d
fix(emojis): stop the emoji grid re-scrolling when categories change
oliverlaz Jul 13, 2026
ccb2772
perf(emojis): isolate emoji cells from hot picker state via a cell co…
oliverlaz Jul 13, 2026
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
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ examples/**/serviceWorker.ts
.agents/
.claude/skills/
skills-lock.json

# Vendored emoji dataset (snapshot of @emoji-mart/data β€” kept minified; regenerate
# via `node scripts/vendor-emoji-data.mjs`)
src/plugins/Emojis/data/emoji-data.json
148 changes: 128 additions & 20 deletions AI.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,26 +241,134 @@ const CustomMessage = () => {

**User intent**: "Add emoji picker and autocomplete"

Emoji support is built into the SDK β€” the **`StreamEmojiPicker`** needs no `emoji-mart`
packages or `init()` call.

> **Deprecation:** the `EmojiPicker` export from `stream-chat-react/emojis` is the legacy
> **emoji-mart-backed** picker. It still works unchanged for backwards compatibility, but
> is **deprecated and will be removed in v15** and logs a one-time console warning. New
> integrations should use `StreamEmojiPicker`; existing emoji-mart integrations keep
> working until you migrate (see _Migrating from emoji-mart_ below).

**Steps**:

1. Install emoji packages: `npm install emoji-mart @emoji-mart/react @emoji-mart/data`
2. Initialize emoji data: `init({ data })` from `emoji-mart`
3. Import `EmojiPicker` from `stream-chat-react/emojis`
4. Pass `EmojiPicker` and `emojiSearchIndex={SearchIndex}` to `Channel`
1. Import `StreamEmojiPicker` from `stream-chat-react/emojis` and pass it to `Channel`.
2. Import the emoji picker's stylesheet: `import 'stream-chat-react/css/emoji-picker.css'`.
It is intentionally **not** part of `index.css` (so apps that don't use the picker
ship no emoji CSS); without it the picker panel renders unstyled.
3. (Optional) For `:shortcode` autocomplete + emoticon replacement, register the
emoji middleware on the message composer's text composer with
`createTextComposerEmojiMiddleware()` (no argument β€” it uses the built-in index).

```tsx
import { EmojiPicker } from 'stream-chat-react/emojis';
import { init, SearchIndex } from 'emoji-mart';
import data from '@emoji-mart/data';

init({ data });
import { StreamEmojiPicker } from 'stream-chat-react/emojis';
import 'stream-chat-react/css/emoji-picker.css';

<Channel EmojiPicker={EmojiPicker} emojiSearchIndex={SearchIndex}>
{/* ... */}
</Channel>;
<Channel EmojiPicker={StreamEmojiPicker}>{/* ... */}</Channel>;
```

**Note**: For React 19, may need package.json overrides for `@emoji-mart/react`
**Notes**:

- Passing a custom `emojiSearchIndex` (including emoji-mart's `SearchIndex`) is
still supported for advanced use.
- On `StreamEmojiPicker`, skin tone and "frequently used" are integrator-managed props
(`skinTone`/`onSkinToneChange`, `frequentlyUsedEmoji`/`onFrequentlyUsedChange`);
the SDK does not persist them. See `examples/vite/` for a localStorage example.
- `StreamEmojiPicker` exposes a small, flat set of props (no emoji-mart-style
`pickerProps` bag): `theme` (`'auto'` β€” the default, inherits the ancestor SDK theme β€”
`'light'`, or `'dark'`), `style`, `categories` (filter + reorder; `'frequent'` always
prepends and cannot be repositioned), `exceptEmojis`, `autoFocus` (default `true`), and
`onClickOutside`. Layout and sizing are CSS, not props: set
`--str-chat__emoji-picker-per-line` (default `9`) for the column count and the other
`--str-chat__emoji-picker-*` tokens for sizing/colors. Emoji-mart-only features (image
`set`s, `custom` emoji, `data`, `i18n` / `locale`, …) aren't available on the built-in
picker β€” keep using the deprecated `EmojiPicker` if you still need them. Try it live in
the "Emoji Picker" settings tab of `examples/vite/`.

- **Custom layouts (composition).** For a non-standard arrangement, or to replace a part,
compose the picker from its slots instead of rearranging props. Rendered directly,
`StreamEmojiPicker` is the preset; `StreamEmojiPicker.Root` + the slots let you assemble
it yourself:

```tsx
import { StreamEmojiPicker, useEmojiPickerContext } from 'stream-chat-react/emojis';

<StreamEmojiPicker.Root onEmojiSelect={insert}>
<StreamEmojiPicker.Nav />
<StreamEmojiPicker.Search />
<StreamEmojiPicker.Grid />
<StreamEmojiPicker.SkinTone />
</StreamEmojiPicker.Root>;
```

The rule: **the SDK owns the data + the selection; you own presentation + behavior.**
`Root` exposes everything through `useEmojiPickerContext()` β€” read-only data
(`categories`, `searchResults`, `status`, `skinTones`, `resolveNative`) and report-back
setters (`selectEmoji`, `setQuery`, `setSkinTone`, `setActiveCategory` /
`requestScrollToCategory`). A custom slot is just a component that calls the hook; when
you replace a slot, that slot's mechanics (scrolling, scroll-spy, roving keyboard, ARIA,
virtualization, hover-preview) become yours, but it keeps talking to the others through
the shared state. Example β€” a paged grid driven by the built-in `Nav`:

```tsx
function PagedGrid() {
const {
categories,
activeCategoryId,
searchResults,
isSearching,
selectEmoji,
resolveNative,
} = useEmojiPickerContext();
const emojis = isSearching
? (searchResults ?? [])
: ((categories.find((c) => c.id === activeCategoryId) ?? categories[0])?.emojis ??
[]);
return (
<div>
{emojis.map((e) => (
<button key={e.id} onClick={() => selectEmoji(e)}>
{resolveNative(e)}
</button>
))}
</div>
);
}
```

`Root` always keeps the container-level a11y (dialog role, Escape-to-close,
focus-return) and the loading/error states, regardless of how you recompose the inside.

- To let users **react with any emoji** (the reaction selector's `+` button), fill
`reactionOptions.extended` with the full emoji set. It also gates display β€” a reaction
whose type isn't in `quick`/`extended` is not rendered. Load it lazily from the emojis
entry (the dataset is code-split, so this adds nothing to your initial bundle):

```tsx
import { defaultReactionOptions, type ReactionOptions } from 'stream-chat-react';
import { loadDefaultExtendedReactionOptions } from 'stream-chat-react/emojis';

const [reactionOptions, setReactionOptions] =
useState<ReactionOptions>(defaultReactionOptions);
useEffect(() => {
loadDefaultExtendedReactionOptions().then((extended) =>
setReactionOptions({ ...defaultReactionOptions, extended }),
);
}, []);

<Channel reactionOptions={reactionOptions}>{/* ... */}</Channel>;
```

For a curated subset instead, build the `extended` map yourself with `mapEmojiMartData`.

- **Migrating from emoji-mart** (the deprecated `EmojiPicker`): swap `EmojiPicker` β†’
`StreamEmojiPicker`, then remove the `emoji-mart` / `@emoji-mart/react` /
`@emoji-mart/data` installs and any `init({ data })` call. The deprecated `EmojiPicker`
takes a raw emoji-mart `pickerProps` bag (e.g. `set`, `emojiSize`); `StreamEmojiPicker`
instead exposes flat props (`theme`, `categories`, `exceptEmojis`, `autoFocus`, …) and
moves layout/sizing to `--str-chat__emoji-picker-*` CSS tokens. Autocomplete
(`createTextComposerEmojiMiddleware`) and reactions (`mapEmojiMartData`) are
engine-agnostic and need no changes.

**Reference**: See `examples/tutorial/src/6-emoji-picker/`

Expand Down Expand Up @@ -365,9 +473,11 @@ body {

**Solution**:

- Ensure emoji packages are installed
- Initialize with `init({ data })` before rendering
- For React 19, add package.json overrides if needed
- Ensure `EmojiPicker` from `stream-chat-react/emojis` is passed to `Channel` (or set
via `ComponentContext`)
- Import the emoji picker CSS: `import 'stream-chat-react/css/emoji-picker.css'`
- For `:shortcode` autocomplete, register `createTextComposerEmojiMiddleware()` on
the composer's text composer

## Resources

Expand All @@ -386,10 +496,8 @@ body {
- `react`: ^19.0.0 || ^18.0.0 || ^17.0.0
- `react-dom`: ^19.0.0 || ^18.0.0 || ^17.0.0
- `stream-chat`: ^9.27.2
- **Optional Dependencies** (for emoji support):
- `emoji-mart`: ^5.4.0
- `@emoji-mart/react`: ^1.1.0
- `@emoji-mart/data`: ^1.1.0
- **Emoji support**: built in via the `stream-chat-react/emojis` entry point β€” no
`emoji-mart` packages required.

## Best Practices

Expand Down
2 changes: 0 additions & 2 deletions examples/tutorial/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
"hoistingLimits": "workspaces"
},
"dependencies": {
"@emoji-mart/data": "^1.2.1",
"emoji-mart": "^5.6.0",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"stream-chat": "^9.49.0",
Expand Down
11 changes: 3 additions & 8 deletions examples/tutorial/src/6-emoji-picker/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ import {
Window,
WithComponents,
} from 'stream-chat-react';
import { EmojiPicker } from 'stream-chat-react/emojis';

import { init, SearchIndex } from 'emoji-mart';
import data from '@emoji-mart/data';
import { StreamEmojiPicker } from 'stream-chat-react/emojis';

import './layout.css';
import { apiKey, tokenProvider, userId, userName } from '../1-client-setup/credentials';
Expand All @@ -32,8 +29,6 @@ const filters: ChannelFilters = {
members: { $in: [userId] },
};

init({ data });

const App = () => {
const [isReady, setIsReady] = useState(false);
const client = useCreateChatClient({
Expand Down Expand Up @@ -66,13 +61,13 @@ const App = () => {

return (
<Chat client={client}>
<WithComponents overrides={{ EmojiPicker }}>
<WithComponents overrides={{ EmojiPicker: StreamEmojiPicker }}>
<ChannelList filters={filters} sort={sort} />
<Channel>
<Window>
<ChannelHeader />
<MessageList />
<MessageComposer emojiSearchIndex={SearchIndex} />
<MessageComposer />
</Window>
<Thread />
</Channel>
Expand Down
4 changes: 4 additions & 0 deletions examples/tutorial/src/6-emoji-picker/layout.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
@layer stream, stream-overrides;
@import 'stream-chat-react/dist/css/index.css' layer(stream);
/* The emoji picker ships its own opt-in stylesheet (kept out of index.css so apps
that don't use it pay no CSS cost). This step renders <EmojiPicker>, so it must
be imported or the picker panel renders unstyled. */
@import 'stream-chat-react/dist/css/emoji-picker.css' layer(stream);

@layer stream-overrides {
.custom-theme {
Expand Down
2 changes: 1 addition & 1 deletion examples/tutorial/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const steps: TutorialStep[] = [
id: 'emoji-picker',
title: '6. Emoji Picker',
description:
'Wire a custom EmojiPicker into MessageComposer with emoji-mart search support.',
'Wire a custom EmojiPicker into MessageComposer with built-in emoji search support.',
Component: EmojiPickerStep,
},
{
Expand Down
1 change: 1 addition & 0 deletions examples/vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"dependencies": {
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"clsx": "^2.1.1",
"emoji-mart": "^5.6.0",
"human-id": "^4.1.3",
Expand Down
Loading