Feature: Emoji picker in the message input toolbar #38

Closed
opened 2026-04-26 16:45:57 +00:00 by icub3d · 0 comments
Owner

Migrated from GitHub issue icub3d/decentcom#45
Original Author: @icub3d
Original Date: 2026-04-15T21:59:45Z


Feature: Emoji picker in the message input toolbar

Overview

Add an emoji picker button to the message input area that opens a popover grid of emojis grouped by category. Clicking an emoji inserts it at the current cursor position in the textarea. The toolbar is designed to be extensible — the emoji picker is the first item, but the structure should accommodate future additions (file attachments, formatting, etc.).

Background

MessageInput (client/src/components/layout/MessageInput.tsx) currently renders only a textarea and a Send button. There is no way to browse or insert emojis other than using the OS emoji keyboard. Adding an in-app picker improves discoverability and keeps the experience consistent across platforms (especially important in Tauri where OS shortcuts vary). The component is deliberately simple today, making it a good candidate for a toolbar extension.

Requirements

  • An emoji button (e.g. 😊) appears to the left of the Send button in the message input bar.
  • Clicking the emoji button opens a popover picker above the input.
  • The picker displays emojis in a scrollable grid, grouped by category (e.g. Smileys & Emotion, People & Body, Animals & Nature, Food & Drink, Travel & Places, Activities, Objects, Symbols).
  • Category tabs at the top of the picker allow jumping to a section.
  • Clicking an emoji inserts it at the current cursor position in the textarea and keeps the textarea focused.
  • The picker closes on outside click, Escape key, or after an emoji is selected.
  • The emoji button and picker are disabled when the input is disabled (not connected / no send permission).
  • Emoji data is bundled statically — no external network requests.

Design

API / Interface Changes

None — emoji insertion is entirely client-side.

Data Model Changes

None.

Component Changes

  • client/src/data/emojis.ts (new) — static emoji data: a typed array of { emoji: string; label: string; category: string } entries covering common emojis across 8 categories. No external library needed for MVP.
  • client/src/components/emoji/EmojiPicker.tsx (new) — popover component:
    • Props: onSelect(emoji: string): void, onClose(): void
    • Category tab bar at the top (scrollable horizontally if needed).
    • Scrollable grid of emoji buttons below.
    • Closes on outside mousedown and keydown Escape (same listener pattern as MemberContextMenu / ServerSidebar).
  • client/src/components/layout/MessageInput.tsx (modified) — changes:
    • Add useRef on the <textarea> to read and restore cursor position.
    • Add a toolbar row (or inline button) containing the emoji picker trigger.
    • On emoji select: splice the emoji into value at the saved cursor offset, update state, restore focus and cursor position.

Cursor Insertion Detail

function insertAtCursor(emoji: string) {
  const el = textareaRef.current;
  const start = el.selectionStart ?? value.length;
  const end   = el.selectionEnd   ?? value.length;
  const next  = value.slice(0, start) + emoji + value.slice(end);
  setValue(next);
  // Restore cursor after emoji in next render cycle
  requestAnimationFrame(() => {
    el.selectionStart = el.selectionEnd = start + emoji.length;
    el.focus();
  });
}

Task List

  • Create client/src/data/emojis.ts with a curated static emoji list grouped by category (~200–300 emojis across 8 categories).
  • Create client/src/components/emoji/EmojiPicker.tsx with category tabs, scrollable emoji grid, outside-click and Escape close logic.
  • Add useRef to the textarea in MessageInput.tsx and implement insertAtCursor.
  • Add the emoji trigger button to MessageInput.tsx; wire open/close state and onSelect handler.
  • Disable the emoji button when the input is disabled.

Test List

  • Unit: EmojiPicker renders category tabs and emoji buttons.
  • Unit: Clicking an emoji calls onSelect with the correct emoji string.
  • Unit: Pressing Escape calls onClose.
  • Unit: Outside mousedown calls onClose.
  • Unit: Each category tab filters the visible emoji grid to that category.
  • Unit: MessageInput — clicking the emoji button opens the picker.
  • Unit: MessageInput — selecting an emoji appends it to the textarea value.
  • Unit: MessageInput — emoji button is absent / disabled when disabled=true.
  • Manual: Open picker, select an emoji mid-sentence → emoji inserted at cursor, not appended.
  • Manual: Open picker, press Escape → picker closes, textarea retains its value.
  • Manual: Open picker, click outside → picker closes.

Open Questions

  • How many emojis should the MVP include? A curated set of ~250 common ones is proposed; the full Unicode emoji list (~3700) can be added later.
  • Should the picker have a search/filter input? Deferred to a follow-up — not required for MVP.
  • Should recently-used emojis be tracked (e.g. in localStorage)? Deferred to a follow-up.
**Migrated from GitHub issue icub3d/decentcom#45** **Original Author:** @icub3d **Original Date:** 2026-04-15T21:59:45Z --- # Feature: Emoji picker in the message input toolbar ## Overview Add an emoji picker button to the message input area that opens a popover grid of emojis grouped by category. Clicking an emoji inserts it at the current cursor position in the textarea. The toolbar is designed to be extensible — the emoji picker is the first item, but the structure should accommodate future additions (file attachments, formatting, etc.). ## Background `MessageInput` (`client/src/components/layout/MessageInput.tsx`) currently renders only a textarea and a Send button. There is no way to browse or insert emojis other than using the OS emoji keyboard. Adding an in-app picker improves discoverability and keeps the experience consistent across platforms (especially important in Tauri where OS shortcuts vary). The component is deliberately simple today, making it a good candidate for a toolbar extension. ## Requirements - [x] An emoji button (e.g. 😊) appears to the left of the Send button in the message input bar. - [x] Clicking the emoji button opens a popover picker above the input. - [x] The picker displays emojis in a scrollable grid, grouped by category (e.g. Smileys & Emotion, People & Body, Animals & Nature, Food & Drink, Travel & Places, Activities, Objects, Symbols). - [x] Category tabs at the top of the picker allow jumping to a section. - [x] Clicking an emoji inserts it at the current cursor position in the textarea and keeps the textarea focused. - [x] The picker closes on outside click, Escape key, or after an emoji is selected. - [x] The emoji button and picker are disabled when the input is disabled (not connected / no send permission). - [x] Emoji data is bundled statically — no external network requests. ## Design ### API / Interface Changes None — emoji insertion is entirely client-side. ### Data Model Changes None. ### Component Changes - **`client/src/data/emojis.ts`** (new) — static emoji data: a typed array of `{ emoji: string; label: string; category: string }` entries covering common emojis across 8 categories. No external library needed for MVP. - **`client/src/components/emoji/EmojiPicker.tsx`** (new) — popover component: - Props: `onSelect(emoji: string): void`, `onClose(): void` - Category tab bar at the top (scrollable horizontally if needed). - Scrollable grid of emoji buttons below. - Closes on outside `mousedown` and `keydown` Escape (same listener pattern as `MemberContextMenu` / `ServerSidebar`). - **`client/src/components/layout/MessageInput.tsx`** (modified) — changes: - Add `useRef` on the `<textarea>` to read and restore cursor position. - Add a toolbar row (or inline button) containing the emoji picker trigger. - On emoji select: splice the emoji into `value` at the saved cursor offset, update state, restore focus and cursor position. ### Cursor Insertion Detail ```ts function insertAtCursor(emoji: string) { const el = textareaRef.current; const start = el.selectionStart ?? value.length; const end = el.selectionEnd ?? value.length; const next = value.slice(0, start) + emoji + value.slice(end); setValue(next); // Restore cursor after emoji in next render cycle requestAnimationFrame(() => { el.selectionStart = el.selectionEnd = start + emoji.length; el.focus(); }); } ``` ## Task List - [x] Create `client/src/data/emojis.ts` with a curated static emoji list grouped by category (~200–300 emojis across 8 categories). - [x] Create `client/src/components/emoji/EmojiPicker.tsx` with category tabs, scrollable emoji grid, outside-click and Escape close logic. - [x] Add `useRef` to the textarea in `MessageInput.tsx` and implement `insertAtCursor`. - [x] Add the emoji trigger button to `MessageInput.tsx`; wire open/close state and `onSelect` handler. - [x] Disable the emoji button when the input is disabled. ## Test List - [x] Unit: `EmojiPicker` renders category tabs and emoji buttons. - [x] Unit: Clicking an emoji calls `onSelect` with the correct emoji string. - [x] Unit: Pressing Escape calls `onClose`. - [x] Unit: Outside `mousedown` calls `onClose`. - [x] Unit: Each category tab filters the visible emoji grid to that category. - [x] Unit: `MessageInput` — clicking the emoji button opens the picker. - [x] Unit: `MessageInput` — selecting an emoji appends it to the textarea value. - [x] Unit: `MessageInput` — emoji button is absent / disabled when `disabled=true`. - [x] Manual: Open picker, select an emoji mid-sentence → emoji inserted at cursor, not appended. - [x] Manual: Open picker, press Escape → picker closes, textarea retains its value. - [x] Manual: Open picker, click outside → picker closes. ## Open Questions - How many emojis should the MVP include? A curated set of ~250 common ones is proposed; the full Unicode emoji list (~3700) can be added later. - Should the picker have a search/filter input? Deferred to a follow-up — not required for MVP. - Should recently-used emojis be tracked (e.g. in localStorage)? Deferred to a follow-up.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
icub3d/decentcom#38
No description provided.