123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- <script lang="ts">
- import { DropdownMenu } from 'bits-ui';
- import { flyAndScale } from '$lib/utils/transitions';
- import emojiGroups from '$lib/emoji-groups.json';
- import emojiShortCodes from '$lib/emoji-shortcodes.json';
- import Tooltip from '$lib/components/common/Tooltip.svelte';
- import VirtualList from '@sveltejs/svelte-virtual-list';
- export let onClose = () => {};
- export let onSubmit = (name) => {};
- export let side = 'top';
- export let align = 'start';
- export let user = null;
- let show = false;
- let emojis = emojiShortCodes;
- let search = '';
- let flattenedEmojis = [];
- let emojiRows = [];
- // Reactive statement to filter the emojis based on search query
- $: {
- if (search) {
- emojis = Object.keys(emojiShortCodes).reduce((acc, key) => {
- if (key.includes(search)) {
- acc[key] = emojiShortCodes[key];
- } else {
- if (Array.isArray(emojiShortCodes[key])) {
- const filtered = emojiShortCodes[key].filter((emoji) => emoji.includes(search));
- if (filtered.length) {
- acc[key] = filtered;
- }
- } else {
- if (emojiShortCodes[key].includes(search)) {
- acc[key] = emojiShortCodes[key];
- }
- }
- }
- return acc;
- }, {});
- } else {
- emojis = emojiShortCodes;
- }
- }
- // Flatten emoji groups and group them into rows of 8 for virtual scrolling
- $: {
- flattenedEmojis = [];
- Object.keys(emojiGroups).forEach((group) => {
- const groupEmojis = emojiGroups[group].filter((emoji) => emojis[emoji]);
- if (groupEmojis.length > 0) {
- flattenedEmojis.push({ type: 'group', label: group });
- flattenedEmojis.push(
- ...groupEmojis.map((emoji) => ({
- type: 'emoji',
- name: emoji,
- shortCodes:
- typeof emojiShortCodes[emoji] === 'string'
- ? [emojiShortCodes[emoji]]
- : emojiShortCodes[emoji]
- }))
- );
- }
- });
- // Group emojis into rows of 6
- emojiRows = [];
- let currentRow = [];
- flattenedEmojis.forEach((item) => {
- if (item.type === 'emoji') {
- currentRow.push(item);
- if (currentRow.length === 7) {
- emojiRows.push(currentRow);
- currentRow = [];
- }
- } else if (item.type === 'group') {
- if (currentRow.length > 0) {
- emojiRows.push(currentRow); // Push the remaining row
- currentRow = [];
- }
- emojiRows.push([item]); // Add the group label as a separate row
- }
- });
- if (currentRow.length > 0) {
- emojiRows.push(currentRow); // Push the final row
- }
- }
- const ROW_HEIGHT = 48; // Approximate height for a row with multiple emojis
- // Handle emoji selection
- function selectEmoji(emoji) {
- const selectedCode = emoji.shortCodes[0];
- onSubmit(selectedCode);
- show = false;
- }
- </script>
- <DropdownMenu.Root
- bind:open={show}
- closeFocus={false}
- onOpenChange={(state) => {
- if (!state) {
- search = '';
- onClose();
- }
- }}
- typeahead={false}
- >
- <DropdownMenu.Trigger>
- <slot />
- </DropdownMenu.Trigger>
- <DropdownMenu.Content
- class="max-w-full w-80 bg-gray-50 dark:bg-gray-850 rounded-lg z-[9999] shadow-lg dark:text-white"
- sideOffset={8}
- {side}
- {align}
- transition={flyAndScale}
- >
- <div class="mb-1 px-3 pt-2 pb-2">
- <input
- type="text"
- class="w-full text-sm bg-transparent outline-none"
- placeholder="Search all emojis"
- bind:value={search}
- />
- </div>
- <!-- Virtualized Emoji List -->
- <div class="w-full flex justify-start h-96 overflow-y-auto px-3 pb-3 text-sm">
- {#if emojiRows.length === 0}
- <div class="text-center text-xs text-gray-500 dark:text-gray-400">No results</div>
- {:else}
- <div class="w-full flex ml-2">
- <VirtualList rowHeight={ROW_HEIGHT} items={emojiRows} height={384} let:item>
- <div class="w-full">
- {#if item.length === 1 && item[0].type === 'group'}
- <!-- Render group header -->
- <div class="text-xs font-medium mb-2 text-gray-500 dark:text-gray-400">
- {item[0].label}
- </div>
- {:else}
- <!-- Render emojis in a row -->
- <div class="flex items-center gap-2 w-full">
- {#each item as emojiItem}
- <Tooltip
- content={emojiItem.shortCodes.map((code) => `:${code}:`).join(', ')}
- placement="top"
- >
- <button
- class="p-1.5 rounded-lg cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700 transition"
- on:click={() => selectEmoji(emojiItem)}
- >
- <img
- src="/assets/emojis/{emojiItem.name.toLowerCase()}.svg"
- alt={emojiItem.name}
- class="size-5"
- loading="lazy"
- />
- </button>
- </Tooltip>
- {/each}
- </div>
- {/if}
- </div>
- </VirtualList>
- </div>
- {/if}
- </div>
- </DropdownMenu.Content>
- </DropdownMenu.Root>
|