Repository navigation
feat(component-library): add BccEmojiPicker, wrap BccTextarea with insertAtCaret - #433
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJ5wpbHr6RegjqdjX2Pm1S
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJ5wpbHr6RegjqdjX2Pm1S
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJ5wpbHr6RegjqdjX2Pm1S
…r prop Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJ5wpbHr6RegjqdjX2Pm1S
Full emoji picker with search, category tabs and keyboard navigation, built on emojibase-data 17. The picker and its data load on first open, and emojis the device can't render are left out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJ5wpbHr6RegjqdjX2Pm1S
…plemented Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJ5wpbHr6RegjqdjX2Pm1S
|
I think on mobile it would be best for those using capacitor already to utilise the capacitor/keyboard plugin and have it open the native emoji keyboard, since opening this on mobile while the input is selected means both the keyboard and the emoji popup will need space to exist at the same time which becomes quite cramped. |
…hat opens it Wait for the opening click to finish before showing the popover, and pass the anchor as currentTarget so clicking it doesn't count as an outside click. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cTextarea and BccInput BccTextarea now wraps PrimeVue Textarea with the same props and emits, and exposes insertAtCaret(text). BccInput exposes the same method (no-op when numeric). Both use a shared helper that fires an input event so v-model and auto-resize update. The emoji picker stories use insertAtCaret and show script setup source examples. The Textarea story moves to Wrapped/BccTextarea. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJ5wpbHr6RegjqdjX2Pm1S
|
@u12206050 Agreed, and that's how it's set up: the picker is only for devices with a mouse or trackpad. The chat hides its emoji button on touch devices, and the native builds don't include the picker, so on mobile the system keyboard (with its own emoji key) is used. As far as I know, Generated by Claude Code |
|
About capacitor keyboard:
As Claude said, we are going to build this emoji picker only for desktop devices, as the ClickUp task (linked in PR description) says. For mobile apps, we have a plan to not even include the picker in the final build, so the bundle size increase will only apply for the web version, and since we are lazy-loading it, it will not affect any others that the ones using the chat on the web version from a desktop device. |
- Keep emojibase-data external in the UMD build, so it no longer inlines ~4 MB of locale JSON (UMD back to ~2.6 MB). It stays a dependency, since UMD consumers now resolve it at runtime. - Keep the picker rendered while the popover fades out, and remount it on each open so the search resets and failed loads are retried. - Show `errorText` when the emoji data fails to load. - Move focus from the top row back to the search field with ArrowUp. - Filter emojibase groups by key instead of their order number. - Add an e2e test for toggling and reopening the popover, and unit tests for the error state and ArrowUp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TU8ibHPhr5xVkZuwzoznz3
There was a problem hiding this comment.
🟡 Changes recommended
Pending opens can survive closure, and data-loading compatibility and error handling remain unresolved.
3 open issues
1 file not reviewed
component-library/pnpm-lock.yaml: Generated file
What changed in this PR
Adds a reusable desktop emoji picker to the component library, plus caret-aware insertion for text fields.
Changes:
- Adds inline and popover pickers with localized search, categories, keyboard navigation, and lazy-loaded data.
- Exposes
insertAtCaretonBccInputand the newly wrappedBccTextarea. - Adds documentation, Storybook examples, and automated tests.
| File | Description |
|---|---|
| component-library/vite.config.umd.ts | Externalizes emoji data in UMD builds. |
| component-library/tests/e2e/emoji-picker.spec.ts | Tests popover toggling and reopening. |
| component-library/src/setup.ts | Registers the app context. |
| component-library/src/index.ts | Removes the direct PrimeVue textarea export. |
| component-library/src/components/wrapped/insertAtCaret.ts | Implements shared caret insertion. |
| component-library/src/components/wrapped/insertAtCaret.spec.ts | Tests insertion and model updates. |
| component-library/src/components/wrapped/index.ts | Exports textarea wrapper and method types. |
| component-library/src/components/wrapped/BccTextarea.vue | Wraps textarea with caret insertion. |
| component-library/src/components/wrapped/BccTextarea.stories.ts | Documents the wrapped textarea. |
| component-library/src/components/wrapped/BccInput.vue | Exposes caret insertion. |
| component-library/src/components/wrapped/BccInput.stories.ts | Demonstrates caret insertion. |
| component-library/src/components/custom/styles.css | Includes picker styles. |
| component-library/src/components/custom/index.ts | Exports picker components and APIs. |
| component-library/src/components/custom/BccEmojiPicker/types.ts | Defines picker options and data types. |
| component-library/src/components/custom/BccEmojiPicker/state.ts | Lazily mounts and controls the popover. |
| component-library/src/components/custom/BccEmojiPicker/data.ts | Loads, filters, and searches emoji data. |
| component-library/src/components/custom/BccEmojiPicker/BccEmojiPickerPopover.vue | Implements anchored popover behavior. |
| component-library/src/components/custom/BccEmojiPicker/BccEmojiPicker.vue | Implements the searchable picker panel. |
| component-library/src/components/custom/BccEmojiPicker/BccEmojiPicker.stories.ts | Adds inline and popover examples. |
| component-library/src/components/custom/BccEmojiPicker/BccEmojiPicker.spec.ts | Tests data and picker interactions. |
| component-library/src/components/custom/BccEmojiPicker/BccEmojiPicker.css | Styles picker layout and states. |
| component-library/src/appContext.ts | Shares context for imperative mounting. |
| component-library/pnpm-lock.yaml | Locks emoji dependencies. |
| component-library/plans/2026-10-emoji-picker.md | Documents design and integration plans. |
| component-library/package.json | Adds the emoji-data dependency. |
Review effort: Balanced
|
|
||
| async function show(next: EmojiPickerOpenOptions) { | ||
| if (isOpen.value) { | ||
| hide(); | ||
| await nextTick(); | ||
| } | ||
| // Once the popover module is loaded this runs inside the click that opened it. Popover binds its | ||
| // outside-click listener on document while that click is still bubbling, so it would close at | ||
| // once. Wait for the event to finish first. | ||
| await new Promise(resolve => setTimeout(resolve)); | ||
| options.value = next; | ||
| isOpen.value = true; | ||
| openCount.value++; | ||
| // Popover treats clicks on `currentTarget` as its own, so clicking the anchor isn't an outside click. | ||
| popover.value?.show({ currentTarget: next.anchor } as unknown as Event, next.anchor); | ||
| } | ||
|
|
||
| function hide() { | ||
| popover.value?.hide(); | ||
| } |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>


Change summary
ClickUp: Støtte emojis og scheduled meldinger på pc i chat (DIGI-6074)
Many desktop users have no emoji keyboard, or don't know the shortcut. Browsers can't open the system emoji panel from a web page. bcc-code/bcc-chat#532 added an emoji button to the chat composer, but it only shows the 36 reaction emojis. The PM wants a normal emoji picker. It lives in the component library so other places in the apps can use it too. The plan is in
component-library/plans/2026-10-emoji-picker.md. Using it in the chat is planned separately in bcc-chat (ui/plans/2026-10-emoji-picker.md): the chat gets anopenEmojiPickerprop, and bcc-event-app passesopenBccEmojiPickerto it in the web build only.The picker is meant for devices with a mouse or trackpad. On phones and tablets the system emoji keyboard is used: the chat hides its emoji button on touch devices, and the native app builds don't include the picker at all.
What's added
openBccEmojiPicker({ anchor, onSelect, locale?, searchPlaceholder?, noResultsText?, onHide? })/closeBccEmojiPicker(): a popover next toanchor(PrimeVuePopover). It stays open so several emojis can be picked, and closes on outside click or Escape. Calling it again with the same anchor closes it, so it works directly as a button's click handler.<BccEmojiPicker @select>: the same panel inline. It has search (names and keywords in the locale), category tabs, a grid grouped by category, arrow-key navigation, Enter selects the first search result, andaria-labels with emoji names. The buttons usemousedown.preventso a text field keeps its caret.BccTextareais now a wrapped component (it was a plain re-export of PrimeVueTextarea). It passes all props, attributes and listeners through unchanged and adds aninsertAtCaret(text)method.BccInputgets the same method; it does nothing whennumeric.insertAtCaretinserts at the caret, replaces a selection, and fires aninputevent sov-modeland auto-resize update. Fields without a selection API (e.g.type="email") get the text appended. Pass it asonSelectto insert emojis into a field.Custom/Input/BccEmojiPickerwith Default, Norwegian and Popover (inserts at the caret in aBccTextarea), with<script setup>source examples.BccTextareamoved fromPrimeVue/BccTextareatoWrapped/BccTextarea. NewBccInputstory: InsertAtCaret.Decisions
emojibase-data, pinned to~17.0.0. Version 18 adds emojis that many devices can't render yet, and uses CLDR beta labels. Rejected:emoji-picker-element(shadow DOM, hard to match design tokens, CDN data by default) andvue3-emoji-picker/emoji-mart(eager data, styling outside the tokens).setup.ts. The popover component and the data load withimport()on first open, one explicit import per locale (en,nb,de,es,fr,nl,pl; others fall back toen). A template import would have emitted all ~30 emojibase locales.src/appContext.ts: PrimeVue components need the app's context when they are mounted withrender().setup.tsstores the app there without importing the picker, so tree-shaking is unaffected.currentTarget. Otherwise PrimeVue counted that click (or a later click on the anchor) as an outside click and closed it at once.BccTextareaprops aren't declared at runtime. Declaring them would turn absent booleans likefluidintofalseand override PrimeVue's defaults.Left out on purpose: skin-tone selection, "recently used", custom emojis. Each can be added without breaking the API.
Compatibility
BccTextarea: existing usage keeps working. bcc-event-app (NpsQuestion.vue) usesv-model,auto-resize,variant,labeland@focus/@blur, which all pass through. A templaterefon it now exposesinsertAtCaretinstead of PrimeVue's instance; neither bcc-event-app nor bcc-chat uses a ref on it.Numbers (measured on the built
distin a probe app)pnpm test:bundle-size(an app with onlyBccBadge): 3.3 kB, same as before, with no emoji files emitted.openBccEmojiPicker: the initial bundle only gets the loader. The popover chunk is ~147 kB raw (includes PrimeVue Popover and InputText). The data is ~570 kB raw / ~83 kB gzip per locale, plus ~80 kB raw for the version list, all loaded on first open.FE0F(e.g. 👍). It looks the same when inserted as text.Test by hand
BccTextareawithauto-resize: inserting emojis grows the field.Checked:
pnpm lint,pnpm typecheck,pnpm test:unit(51 passed, including tests for the picker and forinsertAtCareton both fields), and Playwright against Storybook (search, tabs, arrow keys, Enter, insert at the caret; open, close with the anchor, reopen with the module loaded, outside click and Escape).pnpm build:viteandpnpm test:bundle-sizewere run before theBccTextareacommit.Screenshots
New component, so there is no "before".
After:





Change type
Part of the emoji picker plan (
component-library/plans/2026-10-emoji-picker.md)🤖 Generated with Claude Code
https://claude.ai/code/session_01PJ5wpbHr6RegjqdjX2Pm1S