Pular para o conteúdo principal

Icon system

O front-web-base usa unplugin-icons (Vite plugin) com datasets Iconify pra todos os ícones. lucide-react foi removido em 2026-04-28.

Datasets ativos

DatasetPacoteQuando usar
lucide@iconify-json/lucideParidade com os 146 ícones lucide-react que o projeto usava. Default pra ícones genéricos de UI
simple-icons@iconify-json/simple-iconsBrand logos (WhatsApp, Facebook, X, Telegram, Instagram, TikTok, YouTube, Kwai, Twitch, Threads)
mdi@iconify-json/mdiEsportes específicos (soccer/basketball/tennis/volleyball/controller) e alternativas pros emojis de config (mdi:slot-machine, mdi:trophy, mdi:crown)
customapp/icons/custom/*.svgSVGs próprios via FileSystemIconLoader. Auto-discovered. Pra logos de ligas/brand marks onde Iconify não basta

⚠️ Não nomeie a coleção custom de cactus. AGENTS.md proíbe vazar o termo interno em runtime — custom é o default.

API de consumo

Estático (preferido)

Default import por nome kebab-case. Tree-shaking perfeito, zero runtime.

import Trophy from "~icons/lucide/trophy";
import IconWhatsapp from "~icons/simple-icons/whatsapp";
import IconSoccer from "~icons/mdi/soccer";

<Trophy className="w-4 h-4 text-primary" />
<IconWhatsapp width={20} height={20} className="text-success" />

Nomes kebab-case: AlarmClockalarm-clock, Gamepad2gamepad-2, CheckCircle2check-circle-2.

Dinâmico (string-driven)

Quando o ícone vem de config (ex: icon: "mdi:soccer" em home-leagues), registrar em app/icons/registry.ts e consumir via <Icon>:

// app/icons/registry.ts
export const iconRegistry = {
"mdi:soccer": IconSoccer,
"mdi:basketball": IconBasketball,
// ...
} satisfies Record<string, IconComponent>;

// consumer
import { Icon } from "~/components/ui/Icon";
<Icon name="mdi:soccer" className="w-5 h-5" />

Smart dispatch (config legado com emoji)

Pra configs que misturam IconNames com emojis legados (🎰 🏆), usa <SmartIcon>:

import { SmartIcon } from "~/components/ui/SmartIcon";
<SmartIcon icon={config.icon} />
// renderiza <Icon> se "<set>:<name>" registrado, senão renderiza como texto

Usado em SectionTitleDefault/Gradient/Panel.

Tipos

// app/types/icon.ts
export type IconComponent = (props: SVGProps<SVGSVGElement> & { size?: number | string }) => JSX.Element;

Substitui o antigo LucideIcon (que não existe mais). Componentes gerados pelo unplugin-icons aceitam width/height (não size do Lucide).

Props

⚠️ size do Lucide não funciona com unplugin-icons. Use width={X} height={X}. Codemod (scripts/migrate-icon-size-prop.mjs) já converteu 471 ocorrências.

FlagIcon, SidebarMenuItem, createFlagMenuIcon, TopbarNotification aceitam width/height em vez de size (com size como alias legado mantido em FlagIcon).

Custom SVGs

Drop SVG em app/icons/custom/<filename>.svg → vira componente ~icons/custom/<filename>.

import IconLeaguePlaceholder from "~icons/custom/placeholder-league";
<IconLeaguePlaceholder className="w-12 h-12 text-primary" />

FileSystemIconLoader (configurado em vite.config.ts + vitest.config.ts) auto-injeta fill="currentColor" quando ausente. Garante que o ícone seja tintável via Tailwind theme tokens (text-primary, etc.).

Adicionar ícone novo

  1. Verifica se existe no Iconify antes de codar: https://icon-sets.iconify.design/
  2. Estático: add import direto no componente.
  3. Dinâmico: add entry em app/icons/registry.ts.
  4. Custom: drop SVG em app/icons/custom/.

Gotchas

  • Nem todo ícone Lucide/MDI existe no Iconify com o mesmo nome. Ex: mdi:dragon não existe (dragon caiu como fallback emoji após codemod). Se o build falhar com Icon ... not found, troca o nome ou volta pro emoji.
  • <SmartIcon> com string sem : → renderiza como texto, não como ícone. Use isso intencionalmente pra fallback emoji.
  • Mixed import. Não misture import { X } from "lucide-react" com unplugin-icons. Lucide-react foi removido do package.json.

Performance

  • Cada ícone vira um componente React JSX inline no bundle final (~300-500 bytes gzip).
  • Bundle total pra 146 lucide + 50 mdi + 11 simple-icons ≈ 60-80KB gzip — similar ao lucide-react pre-migration.
  • Zero fetch externo no SSR — todos os ícones vêm como string SVG embutida pelo plugin. Importante pra Cloudflare Workers (sem flash de "ícone vazio" durante hidratação).

Migração completa — referência

Histórico completo das 7 fases (Foundation → Lucide codemod → Quick wins → SocialIcon → Custom SVG pipeline → SmartIcon dispatcher → Emoji codemod) preservado em front-dev/docs/superpowers/archive/2026-04-28-theme-7k-branch-changelog.md (entry "Icon system overhaul").

Scripts reutilizáveis em scripts/:

  • migrate-lucide-to-unplugin-icons.mjs — converte imports de lucide-react.
  • migrate-icon-size-prop.mjs — converte size={X}width={X} height={X}.
  • migrate-emoji-icons.mjs — substitui emojis em icon: "…" literals por IconNames.

Documentação relacionada