3a04eff42b
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.
129 lines
4.4 KiB
TypeScript
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>
|
|
);
|
|
}
|