Skip to content

feat: add custom emoji category support#32

Open
mjcampagna wants to merge 23 commits intoliveblocks:mainfrom
gluegroups:feat/custom-emoji-support
Open

feat: add custom emoji category support#32
mjcampagna wants to merge 23 commits intoliveblocks:mainfrom
gluegroups:feat/custom-emoji-support

Conversation

@mjcampagna
Copy link
Copy Markdown

@mjcampagna mjcampagna commented Mar 16, 2026

Summary

Adds support for image-based custom emoji categories, frequently used emojis, and unified cross-type search — all as opt-in props on <EmojiPicker.Root>.

New Props

Prop Type Default Description
custom CustomCategory[] Image-based emoji categories appended after standard categories.
frequently EmojiPickerEmoji[] Emojis shown in a "Frequently Used" category at the top. Hidden during search.
frequentlyLabel string "Frequently Used" Label for the frequently used category header.
unifiedSearch boolean false When true, all search results are merged into one ranked list.
searchLabel string "" Category header label when unifiedSearch is active.

New Types

type CustomEmoji = { id: string; label: string; url: string; tags?: string[]; };
type CustomCategory = { id: string; label: string; emojis: CustomEmoji[]; };

Both are exported from the package. EmojiPickerRootProps is augmented via intersection (not mutation) so the public type includes all new props.

New Exports

  • CustomEmoji — type for individual custom emoji
  • CustomCategory — type for a custom emoji category
  • scoreEmoji(label, tags, searchText): number — the search scoring function, exported for consumer use

Architecture

All new code lives in dedicated files:

  • src/custom-emoji-types.ts — new types and CustomEmojiRootProps interface
  • src/data/custom-emoji.tsbuildFrequentlyUsedRows, buildCustomCategoryRows, buildUnifiedSearchRows, scoreEmoji
  • src/utils/emoji-identity.tsisSameEmoji for comparing native and custom emojis

Upstream files (src/types.ts, src/data/emoji-picker.ts, src/components/emoji-picker.tsx, src/store.ts) are touched minimally — each change is a delegation call or an optional-param addition at a single call site.

Search Scoring

Custom emoji search mirrors the upstream searchEmojis scoring (+10 label match, +1 per tag match). scoreEmoji is extracted as a shared helper to reduce duplication within our own code.

Unified Search

When unifiedSearch is enabled, buildUnifiedSearchRows merges native and custom emoji results into a single score-ranked list instead of displaying them in separate categories.

Type Widening

EmojiPickerEmoji is widened to { emoji?: string; label: string; url?: string; id?: string } to accommodate both native and custom emojis without casts. The upstream EmojiPickerRootProps export is shadowed in src/index.ts by an augmented version that includes CustomEmojiRootProps.

Bug Fixes

  • sameEmojiPickerEmoji in store.ts: fixed two bugs where custom emojis (with emoji: undefined) would always compare as equal, suppressing useActiveEmoji() updates. Now guards for undefined first and compares custom emojis by id.

Tests

New test files:

  • src/data/__tests__/custom-emoji.test.ts — 15 tests for scoreEmoji, buildFrequentlyUsedRows, buildCustomCategoryRows, buildUnifiedSearchRows
  • src/utils/__tests__/emoji-identity.test.ts — 4 tests for isSameEmoji

Extended:

  • src/data/__tests__/emoji-picker.test.ts — 9 new tests for custom categories, frequently used, and unified search

Docs

See CUSTOM-EMOJIS.md for full usage documentation including examples, prop reference, and type definitions.

Screenshot 2026-03-17 at 8 49 18 PM Screenshot 2026-03-17 at 8 49 27 PM

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread src/components/emoji-picker.tsx
Comment thread src/components/emoji-picker.tsx Outdated
@mjcampagna mjcampagna force-pushed the feat/custom-emoji-support branch from 7097db9 to 2bf6ab4 Compare March 16, 2026 19:25
@cursor
Copy link
Copy Markdown

cursor Bot commented Mar 16, 2026

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@mjcampagna mjcampagna force-pushed the feat/custom-emoji-support branch from 2bf6ab4 to 51ef08a Compare March 16, 2026 19:33
@cursor
Copy link
Copy Markdown

cursor Bot commented Mar 16, 2026

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

feat: add custom emoji category support
@cursor
Copy link
Copy Markdown

cursor Bot commented Mar 17, 2026

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

mjcampagna and others added 3 commits March 16, 2026 18:16
Custom emojis have emoji=undefined, so comparing by emoji string
made all custom emojis highlight simultaneously. Compare by id
for custom emojis, fall back to emoji string for native emojis.

Amp-Thread-ID: https://ampcode.com/threads/T-019cf8a1-c9b3-73c2-9875-c764ee274352
Co-authored-by: Amp <amp@ampcode.com>
Adds a 'frequently' prop to EmojiPicker.Root that accepts
EmojiPickerEmoji[] and prepends them as a 'Frequently Used'
category at the top of the picker. Hidden during search.

Amp-Thread-ID: https://ampcode.com/threads/T-019cf8a1-c9b3-73c2-9875-c764ee274352
Co-authored-by: Amp <amp@ampcode.com>
Comment thread package.json
Comment thread CUSTOM-EMOJIS.md Outdated
Comment thread CUSTOM-EMOJIS.md
@mjcampagna mjcampagna force-pushed the feat/custom-emoji-support branch from e6908d3 to 69e817f Compare March 18, 2026 03:43
@cursor
Copy link
Copy Markdown

cursor Bot commented Mar 18, 2026

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@cursor
Copy link
Copy Markdown

cursor Bot commented Mar 18, 2026

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@mjcampagna mjcampagna requested a review from ym-project March 18, 2026 03:55
@mjcampagna
Copy link
Copy Markdown
Author

@ym-project Thanks for the quick feedback. I've made updates. Let me know if you need anything else.

@ym-project
Copy link
Copy Markdown

@ym-project Thanks for the quick feedback. I've made updates. Let me know if you need anything else.

Unfortunately I'm not a contributor. I was just passing by :)

mjcampagna and others added 6 commits March 25, 2026 16:23
Extract all custom emoji logic out of upstream files and into new
isolated modules (custom-emoji-types.ts, data/custom-emoji.ts,
utils/emoji-identity.ts) so the feature can be removed by deleting
those files and reverting a small number of upstream call sites.

- Restore upstream EmojiPickerEmoji and EmojiPickerRootProps shapes;
  extend via type intersection (CustomEmojiRootProps) instead of
  in-place mutation
- Extract buildFrequentlyUsedRows and buildCustomCategoryRows out of
  getEmojiPickerData into src/data/custom-emoji.ts
- Extract isSameEmoji identity check into src/utils/emoji-identity.ts
- Re-export EmojiPickerRootProps from index.ts as the merged alias so
  the public API is unchanged
- Widen EmojiPickerEmoji with id?, url?, emoji? to eliminate type casts
- Add isolation principles to AGENTS.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the stale 3-file modification list with an accurate description
of the isolated structure: new files, upstream touch points, and a
step-by-step guide for removing the feature entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When searchLabel is provided, search results from native and custom
emojis are merged into a single flat category ranked by relevance
(label +10, tag +1), replacing the default per-category display.
When omitted, upstream search behaviour is preserved.

- Add searchLabel to CustomEmojiRootProps
- Add scoreEmoji() shared scoring helper to custom-emoji.ts
- Refactor searchCustomEmojis() to use scoreEmoji()
- Add buildUnifiedSearchRows() to custom-emoji.ts
- Add early-return unified search branch in getEmojiPickerData()
- Wire searchLabel through EmojiPickerDataHandler and EmojiPickerRoot
- Update AGENTS.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously, unified search was activated by providing searchLabel.
Now unifiedSearch boolean controls the feature and searchLabel is
an independent optional label (defaults to empty string).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mjcampagna and others added 5 commits March 25, 2026 17:17
- src/utils/__tests__/emoji-identity.test.ts: isSameEmoji (native,
  custom, cross-type, undefined inputs)
- src/data/__tests__/custom-emoji.test.ts: scoreEmoji, buildFrequentlyUsedRows,
  buildCustomCategoryRows, buildUnifiedSearchRows
- src/data/__tests__/emoji-picker.test.ts: custom categories, frequently
  used, unified search (enabled/disabled/label)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without this, useActiveEmoji() always returns undefined when hovering
custom emojis — sameEmojiPickerEmoji compared by emoji string, and
custom emojis have emoji: undefined, so undefined === undefined always
returned true, suppressing selector updates.

Mirrors the logic in isSameEmoji (src/utils/emoji-identity.ts).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without the guard, transitioning from no hover (undefined) to a custom
emoji called sameEmojiPickerEmoji(undefined, customEmoji). The id check
didn't fire (a?.id is undefined), falling through to
undefined === undefined → true, still suppressing the update.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…moji, and prop reference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cursor
Copy link
Copy Markdown

cursor Bot commented Mar 26, 2026

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cursor
Copy link
Copy Markdown

cursor Bot commented Mar 26, 2026

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cursor
Copy link
Copy Markdown

cursor Bot commented Mar 26, 2026

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

mjcampagna and others added 2 commits March 31, 2026 11:00
Searching by shortcode (e.g. "white_check_mark") now matches native and
custom emojis even when the shortcode doesn't appear in the label or tags.

- Normalize underscores to spaces in search text so shortcode-style
  queries ("waving_hand") work as phrase matches against labels
- Extend scoreEmoji() with a shortcodes parameter (scored +10); custom
  emojis use their id as a shortcode, native emojis use emojibase data
- Add src/data/shortcodes.ts: fetches en/shortcodes/emojibase.json on
  mount (sessionStorage-cached), exposes getShortcodesForEmoji() keyed
  by Unicode hexcode (variation selectors stripped)
- Kick off loadShortcodes() in EmojiPickerDataHandler alongside the
  existing getEmojiData() call (fire-and-forget, degrades gracefully)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cursor
Copy link
Copy Markdown

cursor Bot commented Mar 31, 2026

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants