diff --git a/src/features/categories/components/category-form-fields.tsx b/src/features/categories/components/category-form-fields.tsx
index 04a02f9..11e3f10 100644
--- a/src/features/categories/components/category-form-fields.tsx
+++ b/src/features/categories/components/category-form-fields.tsx
@@ -1,15 +1,9 @@
"use client";
import { RiMoreLine } from "@remixicon/react";
-import { useState } from "react";
-import { Button } from "@/shared/components/ui/button";
+import { useMemo, useState } from "react";
import { Input } from "@/shared/components/ui/input";
import { Label } from "@/shared/components/ui/label";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/shared/components/ui/popover";
import {
Select,
SelectContent,
@@ -25,6 +19,7 @@ import { getCategoryIconOptions } from "@/shared/lib/categories/icons";
import { cn } from "@/shared/utils/ui";
import { CategoryIcon } from "./category-icon";
+import { CategoryPickerDialog } from "./category-picker-dialog";
import { TypeSelectContent } from "./category-select-items";
import type { CategoryFormValues } from "./types";
@@ -33,17 +28,17 @@ interface CategoryFormFieldsProps {
onChange: (field: keyof CategoryFormValues, value: string) => void;
}
+const iconOptions = getCategoryIconOptions();
+
export function CategoryFormFields({
values,
onChange,
}: CategoryFormFieldsProps) {
- const [popoverOpen, setPopoverOpen] = useState(false);
- const iconOptions = getCategoryIconOptions();
+ const [pickerOpen, setPickerOpen] = useState(false);
- const handleIconSelect = (icon: string) => {
- onChange("icon", icon);
- setPopoverOpen(false);
- };
+ const selectedIconLabel = useMemo(() => {
+ return iconOptions.find((o) => o.value === values.icon)?.label ?? null;
+ }, [values.icon]);
return (
@@ -83,45 +78,36 @@ export function CategoryFormFields({
-
-
+
-
-
-
-
-
-
- {iconOptions.map((option) => (
-
- ))}
-
-
-
-
-
- Escolha um ícone que represente melhor esta categoria.
-
+
+
+
+ {selectedIconLabel ?? "Selecionar ícone"}
+
+
+ Clique para trocar o ícone
+
+
+
+
+
onChange("icon", icon)}
+ />
);
diff --git a/src/features/categories/components/category-icon.tsx b/src/features/categories/components/category-icon.tsx
index 23712c8..f34aad5 100644
--- a/src/features/categories/components/category-icon.tsx
+++ b/src/features/categories/components/category-icon.tsx
@@ -1,20 +1,18 @@
"use client";
-import type { RemixiconComponentType } from "@remixicon/react";
-import * as RemixIcons from "@remixicon/react";
+import type { ComponentType } from "react";
+import { getIconComponent } from "@/shared/utils/icons";
import { cn } from "@/shared/utils/ui";
-const ICONS = RemixIcons as Record;
-const FALLBACK_ICON = ICONS.RiPriceTag3Line;
-
interface CategoryIconProps {
name?: string | null;
className?: string;
}
export function CategoryIcon({ name, className }: CategoryIconProps) {
- const IconComponent =
- (name ? ICONS[name] : undefined) ?? FALLBACK_ICON ?? null;
+ const IconComponent = (
+ name ? getIconComponent(name) : getIconComponent("RiPriceTag3Line")
+ ) as ComponentType<{ className?: string }> | null;
if (!IconComponent) {
return (
diff --git a/src/features/categories/components/category-picker-dialog.tsx b/src/features/categories/components/category-picker-dialog.tsx
new file mode 100644
index 0000000..91f73da
--- /dev/null
+++ b/src/features/categories/components/category-picker-dialog.tsx
@@ -0,0 +1,117 @@
+"use client";
+
+import { useMemo, useState } from "react";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "@/shared/components/ui/dialog";
+import { Input } from "@/shared/components/ui/input";
+import { CATEGORY_ICON_GROUPS } from "@/shared/lib/categories/icons";
+import { cn } from "@/shared/utils/ui";
+
+import { CategoryIcon } from "./category-icon";
+
+interface CategoryPickerDialogProps {
+ open: boolean;
+ value: string;
+ onOpenChange: (open: boolean) => void;
+ onSelect: (icon: string) => void;
+}
+
+export function CategoryPickerDialog({
+ open,
+ value,
+ onOpenChange,
+ onSelect,
+}: CategoryPickerDialogProps) {
+ const [search, setSearch] = useState("");
+
+ const handleOpenChange = (isOpen: boolean) => {
+ if (!isOpen) setSearch("");
+ onOpenChange(isOpen);
+ };
+
+ const filteredGroups = useMemo(() => {
+ const query = search.toLowerCase().trim();
+ if (!query) return CATEGORY_ICON_GROUPS;
+
+ return CATEGORY_ICON_GROUPS.flatMap((group) => {
+ const icons = group.icons.filter(
+ (icon) =>
+ icon.label.toLowerCase().includes(query) ||
+ group.label.toLowerCase().includes(query),
+ );
+ return icons.length > 0 ? [{ ...group, icons }] : [];
+ });
+ }, [search]);
+
+ const totalVisible = filteredGroups.reduce(
+ (acc, g) => acc + g.icons.length,
+ 0,
+ );
+
+ return (
+
+ );
+}
diff --git a/src/shared/lib/categories/icons.ts b/src/shared/lib/categories/icons.ts
index d73cf39..abe8a3d 100644
--- a/src/shared/lib/categories/icons.ts
+++ b/src/shared/lib/categories/icons.ts
@@ -156,6 +156,186 @@ export const CATEGORY_ICON_OPTIONS: CategoryIconOption[] = [
{ label: "Nuvem Upload", value: "RiCloudUploadLine" },
];
+export type CategoryIconGroup = {
+ label: string;
+ icons: CategoryIconOption[];
+};
+
+export const CATEGORY_ICON_GROUPS: CategoryIconGroup[] = [
+ {
+ label: "Finanças",
+ icons: [
+ { label: "Dinheiro", value: "RiMoneyDollarCircleLine" },
+ { label: "Carteira", value: "RiWallet3Line" },
+ { label: "Carteira 2", value: "RiWalletLine" },
+ { label: "Cartão", value: "RiBankCard2Line" },
+ { label: "Banco", value: "RiBankLine" },
+ { label: "Moedas", value: "RiHandCoinLine" },
+ { label: "Gráfico", value: "RiLineChartLine" },
+ { label: "Ações", value: "RiStockLine" },
+ { label: "Troca", value: "RiExchangeLine" },
+ { label: "Reembolso", value: "RiRefundLine" },
+ { label: "Recompensa", value: "RiRefund2Line" },
+ { label: "Leilão", value: "RiAuctionLine" },
+ ],
+ },
+ {
+ label: "Compras",
+ icons: [
+ { label: "Carrinho", value: "RiShoppingCartLine" },
+ { label: "Sacola", value: "RiShoppingBagLine" },
+ { label: "Cesta", value: "RiShoppingBasketLine" },
+ { label: "Presente", value: "RiGiftLine" },
+ { label: "Cupom", value: "RiCouponLine" },
+ { label: "Ticket", value: "RiTicket2Line" },
+ ],
+ },
+ {
+ label: "Alimentação",
+ icons: [
+ { label: "Restaurante", value: "RiRestaurantLine" },
+ { label: "Garfo e faca", value: "RiRestaurant2Line" },
+ { label: "Café", value: "RiCupLine" },
+ { label: "Bebida", value: "RiDrinksFill" },
+ { label: "Pizza", value: "RiCake3Line" },
+ { label: "Cerveja", value: "RiBeerLine" },
+ ],
+ },
+ {
+ label: "Transporte",
+ icons: [
+ { label: "Ônibus", value: "RiBusLine" },
+ { label: "Carro", value: "RiCarLine" },
+ { label: "Táxi", value: "RiTaxiLine" },
+ { label: "Moto", value: "RiMotorbikeLine" },
+ { label: "Avião", value: "RiFlightTakeoffLine" },
+ { label: "Navio", value: "RiShipLine" },
+ { label: "Trem", value: "RiTrainLine" },
+ { label: "Metrô", value: "RiSubwayLine" },
+ { label: "Bicicleta", value: "RiBikeLine" },
+ { label: "Mapa", value: "RiMapPinLine" },
+ { label: "Combustível", value: "RiGasStationLine" },
+ ],
+ },
+ {
+ label: "Moradia",
+ icons: [
+ { label: "Casa", value: "RiHomeLine" },
+ { label: "Prédio", value: "RiBuilding2Line" },
+ { label: "Apartamento", value: "RiBuildingLine" },
+ { label: "Ferramentas", value: "RiToolsLine" },
+ { label: "Lâmpada", value: "RiLightbulbLine" },
+ { label: "Energia", value: "RiFlashlightLine" },
+ ],
+ },
+ {
+ label: "Saúde e bem-estar",
+ icons: [
+ { label: "Saúde", value: "RiStethoscopeLine" },
+ { label: "Hospital", value: "RiHospitalLine" },
+ { label: "Coração", value: "RiHeart2Line" },
+ { label: "Pulso", value: "RiHeartPulseLine" },
+ { label: "Mental", value: "RiMentalHealthLine" },
+ { label: "Farmácia", value: "RiFirstAidKitLine" },
+ { label: "Fitness", value: "RiRunLine" },
+ ],
+ },
+ {
+ label: "Educação",
+ icons: [
+ { label: "Livro", value: "RiBook2Line" },
+ { label: "Graduação", value: "RiGraduationCapLine" },
+ { label: "Escola", value: "RiSchoolLine" },
+ { label: "Lápis", value: "RiPencilLine" },
+ ],
+ },
+ {
+ label: "Trabalho",
+ icons: [
+ { label: "Maleta", value: "RiBriefcaseLine" },
+ { label: "Pasta", value: "RiBriefcase4Line" },
+ { label: "Escritório", value: "RiUserStarLine" },
+ ],
+ },
+ {
+ label: "Lazer",
+ icons: [
+ { label: "Controle", value: "RiGamepadLine" },
+ { label: "Filme", value: "RiMovie2Line" },
+ { label: "Música", value: "RiMusic2Line" },
+ { label: "Microfone", value: "RiMicLine" },
+ { label: "Fone", value: "RiHeadphoneLine" },
+ { label: "Câmera", value: "RiCameraLine" },
+ { label: "Praia", value: "RiUmbrellaLine" },
+ { label: "Futebol", value: "RiFootballLine" },
+ { label: "Basquete", value: "RiBasketballLine" },
+ ],
+ },
+ {
+ label: "Tecnologia",
+ icons: [
+ { label: "WiFi", value: "RiWifiLine" },
+ { label: "Celular", value: "RiSmartphoneLine" },
+ { label: "Computador", value: "RiComputerLine" },
+ { label: "Monitor", value: "RiMonitorLine" },
+ { label: "Teclado", value: "RiKeyboardLine" },
+ { label: "Mouse", value: "RiMouseLine" },
+ { label: "Fone Bluetooth", value: "RiBluetoothLine" },
+ ],
+ },
+ {
+ label: "Pessoas",
+ icons: [
+ { label: "Usuário", value: "RiUserLine" },
+ { label: "Grupo", value: "RiGroupLine" },
+ { label: "Família", value: "RiParentLine" },
+ { label: "Bebê", value: "RiBabyCarriageLine" },
+ ],
+ },
+ {
+ label: "Outros",
+ icons: [
+ { label: "Animais", value: "RiBearSmileLine" },
+ { label: "Camiseta", value: "RiTShirtLine" },
+ { label: "Arquivo", value: "RiFileTextLine" },
+ { label: "Documento", value: "RiArticleLine" },
+ { label: "Balança", value: "RiScales2Line" },
+ { label: "Escudo", value: "RiShieldCheckLine" },
+ { label: "Serviço", value: "RiServiceLine" },
+ { label: "Alerta", value: "RiAlertLine" },
+ { label: "Troféu", value: "RiMedalLine" },
+ { label: "Mais", value: "RiMore2Line" },
+ { label: "Estrela", value: "RiStarLine" },
+ { label: "Foguete", value: "RiRocketLine" },
+ { label: "Ampulheta", value: "RiHourglassLine" },
+ { label: "Calendário", value: "RiCalendarLine" },
+ { label: "Relógio", value: "RiTimeLine" },
+ { label: "Timer", value: "RiTimer2Line" },
+ { label: "Fogo", value: "RiFireLine" },
+ { label: "Gota", value: "RiDropLine" },
+ { label: "Sol", value: "RiSunLine" },
+ { label: "Lua", value: "RiMoonLine" },
+ { label: "Nuvem", value: "RiCloudLine" },
+ { label: "Raio", value: "RiFlashlightFill" },
+ { label: "Planta", value: "RiPlantLine" },
+ { label: "Árvore", value: "RiSeedlingLine" },
+ { label: "Globo", value: "RiGlobalLine" },
+ { label: "Localização", value: "RiMapPin2Line" },
+ { label: "Bússola", value: "RiCompassLine" },
+ { label: "Reciclagem", value: "RiRecycleLine" },
+ { label: "Cadeado", value: "RiLockLine" },
+ { label: "Chave", value: "RiKeyLine" },
+ { label: "Configurações", value: "RiSettings3Line" },
+ { label: "Link", value: "RiLinkLine" },
+ { label: "Anexo", value: "RiAttachmentLine" },
+ { label: "Download", value: "RiDownloadLine" },
+ { label: "Upload", value: "RiUploadLine" },
+ { label: "Nuvem Download", value: "RiCloudDownloadLine" },
+ { label: "Nuvem Upload", value: "RiCloudUploadLine" },
+ ],
+ },
+];
+
/**
* Gets all available category icon options
* @returns Array of icon options