Files
dumpsterChat/web/src/components/EmojiPicker.tsx
T
hobokenchicken 3a04eff42b feat: kaomoji picker with full kaomojikuma.com library
34 categories, 3300+ kaomoji from kaomojikuma.com.
Data in web/src/lib/kaomojiData.ts (exported KAOMOJI_CATEGORIES).
EmojiPicker refactored to import from external data file.
Categories: Greetings, Joy, Sad, Mad, Stress, Love, Animals,
Action, Shy, OwO, OMG, Neutral, Kawaii, Wave, Bodily, Table,
Fight, Magic, Dance, Sleep, Run, Music, Hugs, Kiss, Wink,
Heart, Rage, Thumbs, Cheer, Drink, Write, Comfort, Flourishes.
Search by emoji text or tags. Tabbed browsing.
2026-07-02 12:04:22 -04:00

129 lines
4.4 KiB
TypeScript

import { useState, useMemo } from 'react';
import { KAOMOJI_CATEGORIES, type KaomojiItem } from '../lib/kaomojiData';
interface EmojiPickerProps {
onSelect: (emoji: string) => void;
onClose: () => void;
}
export function EmojiPicker({ onSelect, onClose }: EmojiPickerProps) {
const [search, setSearch] = useState('');
const [activeTab, setActiveTab] = useState('All');
// Search logic (checks emoji or tags matching)
const searchResults = useMemo(() => {
if (!search) return [];
const query = search.toLowerCase().trim();
const results: KaomojiItem[] = [];
const seen = new Set<string>();
for (const cat of KAOMOJI_CATEGORIES) {
for (const item of cat.items) {
if (seen.has(item.emoji)) continue;
const matchesEmoji = item.emoji.toLowerCase().includes(query);
const matchesTags = item.tags.some(tag => tag.includes(query));
if (matchesEmoji || matchesTags) {
results.push(item);
seen.add(item.emoji);
}
}
}
return results;
}, [search]);
// Tab filter logic
const displayedItems = useMemo(() => {
if (activeTab === 'All') {
const all: KaomojiItem[] = [];
const seen = new Set<string>();
for (const cat of KAOMOJI_CATEGORIES) {
for (const item of cat.items) {
if (!seen.has(item.emoji)) {
all.push(item);
seen.add(item.emoji);
}
}
}
return all;
}
const cat = KAOMOJI_CATEGORIES.find(c => c.tabLabel === activeTab);
return cat ? cat.items : [];
}, [activeTab]);
return (
<div className="absolute bottom-full left-0 mb-1 bg-gb-bg border border-gb-bg-t p-2 w-[420px] max-h-[400px] overflow-y-auto z-50 shadow-lg font-mono">
<div className="flex items-center justify-between mb-2">
<span className="text-xs text-gb-fg-f">KAOMOJI PICKER</span>
<button onClick={onClose} className="text-xs text-gb-red hover:text-gb-orange font-mono">[x]</button>
</div>
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="search (happy, bear, sad, table...)"
className="w-full px-2 py-1 mb-2 text-xs bg-gb-bg-s text-gb-fg border border-gb-bg-t outline-none focus:border-gb-orange"
autoFocus
/>
{/* Tabs list (only shown if not searching) */}
{!search && (
<div className="flex flex-wrap gap-1 mb-2 border-b border-gb-bg-t pb-1.5 text-[10px]">
<button
onClick={() => setActiveTab('All')}
className={`px-1.5 py-0.5 border ${activeTab === 'All' ? 'border-gb-orange text-gb-orange bg-gb-bg-s' : 'border-transparent text-gb-fg-f hover:text-gb-fg'}`}
>
[All]
</button>
{KAOMOJI_CATEGORIES.map(cat => (
<button
key={cat.tabLabel}
onClick={() => setActiveTab(cat.tabLabel)}
className={`px-1.5 py-0.5 border ${activeTab === cat.tabLabel ? 'border-gb-orange text-gb-orange bg-gb-bg-s' : 'border-transparent text-gb-fg-f hover:text-gb-fg'}`}
>
[{cat.tabLabel}]
</button>
))}
</div>
)}
{/* Emoticons grid */}
<div className="flex flex-wrap gap-1.5 max-h-64 overflow-y-auto">
{search ? (
searchResults.map((item) => (
<button
key={item.emoji}
onClick={() => {
onSelect(item.emoji);
onClose();
}}
className="px-2 py-1 text-xs bg-gb-bg-s text-gb-fg border border-gb-bg-t hover:border-gb-orange hover:text-gb-orange transition-colors"
title={item.tags.join(', ')}
>
{item.emoji}
</button>
))
) : (
displayedItems.map((item) => (
<button
key={item.emoji}
onClick={() => {
onSelect(item.emoji);
onClose();
}}
className="px-2 py-1 text-xs bg-gb-bg-s text-gb-fg border border-gb-bg-t hover:border-gb-orange hover:text-gb-orange transition-colors"
title={item.tags.join(', ')}
>
{item.emoji}
</button>
))
)}
</div>
{search && searchResults.length === 0 && (
<div className="text-xs text-gb-fg-f text-center py-4">[no matches found]</div>
)}
</div>
);
}