Add custom emoji support to frimousse so glue-web can inject custom emoji categories (image-based) into the picker. Changes should be minimal and additive to keep rebasing on upstream easy.
New files added by this fork. Zero merge conflict surface area — upstream rebase never touches these.
| File | Purpose |
|---|---|
src/custom-emoji-types.ts |
CustomEmoji, CustomCategory, CustomEmojiRootProps, AugmentedEmojiPickerRootProps |
src/data/custom-emoji.ts |
buildFrequentlyUsedRows(), buildCustomCategoryRows(), buildUnifiedSearchRows(), scoreEmoji(), searchCustomEmojis() |
src/utils/emoji-identity.ts |
isSameEmoji() — discriminated identity check for native vs. custom emojis |
Minimal changes to upstream files. Each is a small, targeted insertion.
EmojiPickerEmoji: widened withemoji?,url?,id?to accommodate custom emojis flowing through the upstream data pipeline (required because$activeEmojiinstore.tsreturns this type and cannot be modified)- Re-exports
CustomEmojiandCustomCategoryfromcustom-emoji-types.ts
getEmojiPickerData(): five new optional params (custom,frequently,frequentlyLabel,unifiedSearch,searchLabel)- When
searchis non-empty and bothcustomandunifiedSearchare truthy, delegates immediately tobuildUnifiedSearchRows()and early-returns a single flat category (unified ranking across native and custom emojis);searchLabelsets the category header (defaults to"") - Otherwise: two delegation call sites — one for frequently used rows (before the native emoji loop), one for custom category rows (after it). All logic lives in
custom-emoji.ts.
sameEmojiPickerEmoji: updated to compare byidwhen both sides have one, falling back toemojistring. Without this,useActiveEmoji()always returnsundefinedfor custom emojis becauseundefined === undefinedsuppresses selector updates. MirrorsisSameEmojiinemoji-identity.ts.
EmojiPickerRootandEmojiPickerDataHandler: prop type changed toEmojiPickerRootProps & CustomEmojiRootProps; props forwarded togetEmojiPickerData()EmojiPickerListEmoji:isActiveselector replaced withisSameEmoji()call
CustomEmoji,CustomCategoryadded to exportsEmojiPickerRootPropsre-exported asAugmentedEmojiPickerRootProps(the merged type), shadowing the upstream export so consumers see the full prop surface
To strip the custom emoji feature entirely:
-
Delete
src/custom-emoji-types.ts,src/data/custom-emoji.ts,src/utils/emoji-identity.ts -
Revert
src/types.ts:- Remove the re-exports of
CustomEmojiandCustomCategory - Restore
EmojiPickerEmojito{ emoji: string; label: string }
- Remove the re-exports of
-
Revert
src/data/emoji-picker.ts:- Remove the
custom,frequently,frequentlyLabel,unifiedSearch,searchLabelparams fromgetEmojiPickerData() - Remove the unified search early-return branch and the two delegation call sites, and their imports
- Remove the
-
Revert
src/store.ts:- Restore
sameEmojiPickerEmojitoreturn a?.emoji === b?.emoji
- Restore
-
Revert
src/components/emoji-picker.tsx:- Remove
CustomEmojiRootPropsimport and type intersections; restoreEmojiPickerRootPropsalone - Remove
isSameEmojiimport; restoreisActiveto$activeEmoji(state)?.emoji === emoji.emoji - Remove destructuring and forwarding of
custom,frequently,frequentlyLabel,unifiedSearch,searchLabel
- Remove
-
Revert
src/index.ts:- Remove
CustomEmoji,CustomCategoryexports - Restore
EmojiPickerRootPropsto export directly from./types
- Remove
The DefaultEmojiPickerListEmoji is not modified. Consumers render custom emoji images via the existing components prop on <EmojiPicker.List>:
<EmojiPicker.List
components={{
Emoji: ({ emoji, ...props }) => (
<button {...props}>
{emoji.url ? (
<img src={emoji.url} alt={emoji.label} style={{ width: "1em", height: "1em" }} />
) : (
emoji.emoji
)}
</button>
),
}}
/>- Publish as
@gluegroups/frimousseto GitHub Packages - Consumed in glue-web via existing
.yarnrc.yml@gluegroupsscope config mainbranch uses fork-specificpackage.jsonvalues:"name": "@gluegroups/frimousse""version": our own version (e.g."0.3.4")"repository.url":"git+https://github.com/gluegroups/frimousse.git"
When pushing branches that target liveblocks/frimousse, revert package.json to upstream values:
"name": "frimousse""version": match upstream (e.g."0.3.0")"repository.url":"git+https://github.com/liveblocks/frimousse.git"
Do not include AGENTS.md or any @gluegroups-specific references in upstream PRs.
This is a fork. Every change we make is a future merge conflict. Follow these principles to keep upstream rebases clean and painless:
-
Isolate into new files. Prefer adding new files (e.g.
src/data/custom-emojis.ts) over editing existing ones. New files have zero merge conflict surface area. -
Extract before inserting. When logic must touch an existing file, extract it into a self-contained function first, then call that function from the existing code at a single, minimal call site. The insertion point should be as small as possible — ideally one line.
-
Make it trivially removable. Any change should be removable by deleting our new files and reverting a small number of call sites. If removing a feature requires untangling logic scattered across an existing file, the change was not isolated enough.
-
No refactoring of upstream code. Do not rename, reorganize, or restructure upstream code, even if it would be cleaner. Each such change is a rebase hazard.
-
Avoid touching upstream types where possible. Prefer extending types via intersection or wrapping rather than modifying upstream type definitions in place.
Keep changes isolated and additive so rebasing on upstream/main stays clean:
git fetch upstream
git rebase upstream/main
# Re-apply @gluegroups name/version/repo in package.json
# Run tests, then publish