Files
fintrack/components/ui/icon-picker.tsx

336 lines
6.5 KiB
TypeScript

"use client";
import { useState, useMemo } from "react";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandList,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { CategoryIcon } from "@/components/ui/category-icon";
import { ChevronsUpDown } from "lucide-react";
import { cn } from "@/lib/utils";
// Group icons by category for better organization
const iconGroups: Record<string, string[]> = {
Alimentation: [
"shopping-cart",
"utensils",
"croissant",
"coffee",
"wine",
"beer",
"pizza",
"apple",
"cherry",
"salad",
"sandwich",
"ice-cream",
"cake",
"cup-soda",
"milk",
"egg",
"fish",
"beef",
],
Transport: [
"fuel",
"train",
"car",
"parking",
"bike",
"plane",
"bus",
"ship",
"sailboat",
"truck",
"car-front",
"circle-parking",
"train-front",
],
Logement: [
"home",
"zap",
"droplet",
"hammer",
"sofa",
"refrigerator",
"washing-machine",
"lamp",
"lamp-desk",
"armchair",
"bath",
"shower-head",
"door-open",
"fence",
"trees",
"flower",
"leaf",
"sun",
"snowflake",
"wind",
"thermometer",
],
Santé: [
"pill",
"stethoscope",
"hospital",
"glasses",
"dumbbell",
"sparkles",
"heart",
"heart-pulse",
"activity",
"syringe",
"bandage",
"brain",
"eye",
"ear",
"hand",
"footprints",
"person-standing",
],
Loisirs: [
"tv",
"music",
"film",
"gamepad",
"book",
"ticket",
"clapperboard",
"headphones",
"speaker",
"radio",
"camera",
"image",
"palette",
"brush",
"pen-tool",
"scissors",
"drama",
"party-popper",
],
Sport: ["trophy", "medal", "target", "volleyball"],
Shopping: [
"shirt",
"smartphone",
"package",
"shopping-bag",
"store",
"gem",
"watch",
"sunglasses",
"crown",
"laptop",
"monitor",
"keyboard",
"mouse",
"printer",
"tablet-smartphone",
"headset",
],
Services: [
"wifi",
"repeat",
"landmark",
"shield",
"receipt",
"file-text",
"mail",
"phone",
"message-square",
"send",
"globe",
"cloud",
"server",
"lock",
"unlock",
"settings",
"wrench",
],
Finance: [
"piggy-bank",
"banknote",
"wallet",
"hand-coins",
"undo",
"coins",
"credit-card",
"building",
"building2",
"trending-up",
"trending-down",
"bar-chart",
"pie-chart",
"line-chart",
"calculator",
"percent",
"dollar-sign",
"euro",
],
Voyage: [
"bed",
"luggage",
"map",
"map-pin",
"compass",
"mountain",
"tent",
"palmtree",
"umbrella",
"globe2",
"flag",
],
Famille: [
"graduation-cap",
"baby",
"paw-print",
"users",
"user",
"user-plus",
"dog",
"cat",
"bird",
"rabbit",
],
Autre: [
"heart-handshake",
"gift",
"cigarette",
"arrow-right-left",
"help-circle",
"tag",
"folder",
"key",
"star",
"bookmark",
"clock",
"calendar",
"bell",
"alert-triangle",
"info",
"check-circle",
"x-circle",
"plus",
"minus",
"search",
"trash",
"edit",
"download",
"upload",
"share",
"link",
"paperclip",
"archive",
"box",
"boxes",
"container",
"briefcase",
"education",
"award",
"lightbulb",
"flame",
"rocket",
"atom",
],
};
interface IconPickerProps {
value: string;
onChange: (icon: string) => void;
color?: string;
}
export function IconPicker({ value, onChange, color }: IconPickerProps) {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
// Filter icons based on search
const filteredGroups = useMemo(() => {
if (!search.trim()) return iconGroups;
const query = search.toLowerCase();
const result: Record<string, string[]> = {};
Object.entries(iconGroups).forEach(([group, icons]) => {
const filtered = icons.filter(
(icon) =>
icon.toLowerCase().includes(query) ||
group.toLowerCase().includes(query),
);
if (filtered.length > 0) {
result[group] = filtered;
}
});
return result;
}, [search]);
const handleSelect = (icon: string) => {
onChange(icon);
setOpen(false);
setSearch("");
};
return (
<Popover open={open} onOpenChange={setOpen} modal={true}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between"
>
<div className="flex items-center gap-2">
<CategoryIcon icon={value} color={color} size={20} />
<span className="text-muted-foreground text-sm">{value}</span>
</div>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent
className="w-[350px] p-0"
align="start"
onOpenAutoFocus={(e) => e.preventDefault()}
>
<Command>
<CommandInput
placeholder="Rechercher une icône..."
value={search}
onValueChange={setSearch}
/>
<CommandList className="max-h-[300px]">
<CommandEmpty>Aucune icône trouvée.</CommandEmpty>
{Object.entries(filteredGroups).map(([group, icons]) => (
<CommandGroup key={group} heading={group}>
<div className="grid grid-cols-6 gap-1 p-1">
{icons.map((icon) => (
<button
key={icon}
onClick={() => handleSelect(icon)}
className={cn(
"flex items-center justify-center p-2 rounded-md hover:bg-accent transition-colors",
value === icon && "bg-accent ring-2 ring-primary",
)}
title={icon}
>
<CategoryIcon icon={icon} color={color} size={20} />
</button>
))}
</div>
</CommandGroup>
))}
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}