Ícones em React Native
O React Native não tem um elemento SVG próprio, então os ícones do glyphfox são renderizados pelo react-native-svg. O motor do glyphfox transforma os dados do ícone em uma string SVG, e o SvgXml a desenha de forma nativa no iOS e no Android.
Instalação
Instale o glyphfox e o react-native-svg. Em um projeto Expo, deixe o Expo escolher a versão do react-native-svg compatível com o seu SDK; em um projeto bare, instale os pods do iOS em seguida.
npm install glyphfox react-native-svg # Expo npx expo install react-native-svg # bare React Native, iOS npx pod-install
Os componentes React de glyphfox/react não servem para React Native: eles renderizam elementos <svg> do DOM, e o React Native não tem nenhum host component com esse nome. Use o motor do core, descrito abaixo.
Como funciona
renderToString(icon, options) de glyphfox/core recebe os dados do ícone e as props compartilhadas e retorna uma string SVG completa. Tudo o que muda a geometria acontece em JavaScript antes de a string existir: o eixo de peso com a compensação óptica, cantos redondos ou retos, os estilos duotone e sólido, rotação e espelhamento, selos modificadores com o espaço de recorte, e fusões. Depois, o react-native-svg faz o parse da string uma única vez e desenha formas nativas.
O que não sobrevive é tudo o que depende de CSS em um navegador: as animações de estados vivos, o morph só com CSS e a classe de espelhamento da direita para a esquerda. A seção de limitações, mais abaixo, lista cada um deles com o seu substituto nativo.
Um pequeno componente Glyph
Envolva o SvgXml uma vez e use em todo lugar. A string é memoizada, então renderizar o pai de novo não faz o parse do SVG outra vez, e a cor é passada ao SvgXml como prop, para que uma troca de tema não reconstrua a string.
import { useMemo } from 'react'; import { SvgXml } from 'react-native-svg'; import { renderToString } from 'glyphfox/core'; import type { IconData, SvgOptions } from 'glyphfox'; type Props = Omit<SvgOptions, 'size' | 'color'> & { icon: IconData; size?: number; color?: string }; export function Glyph({ icon, size = 24, color = '#121014', ...options }: Props) { const key = JSON.stringify(options); const xml = useMemo(() => renderToString(icon, { ...options, size }), [icon, size, key]); return <SvgXml xml={xml} width={size} height={size} color={color} />; }
import { View } from 'react-native'; import home from 'glyphfox/icons/home'; import bell from 'glyphfox/icons/bell'; import folder from 'glyphfox/icons/folder'; import { Glyph } from './Glyph'; export function Toolbar() { return ( <View style={{ flexDirection: 'row', gap: 16 }}> <Glyph icon={home} size={22} color="#ff7a1a" /> <Glyph icon={bell} modifier="dot" weight={1.5} /> <Glyph icon={folder} variant="duotone" /> </View> ); }
Os traços usam currentColor, e o react-native-svg resolve esse valor a partir da prop color. Acompanhe o tema do sistema com useColorScheme() e passe uma cor diferente; a string SVG em cache continua a mesma.
Mantenha o bundle pequeno
O Metro não faz tree shaking. Tudo o que um módulo importa vai parar no bundle do app, então a forma como você importa os ícones define quanto código é entregue:
| Importação | O que é entregue | Indicado para |
|---|---|---|
glyphfox/core mais glyphfox/icons/NAME | O motor e os ícones que você importa, algumas centenas de bytes por ícone | A maioria dos apps |
toSvg de glyphfox | O motor e todos os ícones do conjunto | Protótipos, ou ícones escolhidos pelo nome em tempo de execução |
| Um pacote de subconjunto gerado com a CLI | Só strings SVG prontas, sem motor nenhum | Um conjunto fixo de ícones com props fixas |
A CLI transforma uma lista de ícones em um pacote pequeno cujos exports são strings SVG simples, com as props já aplicadas no build. Adicione a pasta ao seu app como workspace ou como dependência file: e passe as strings para o SvgXml. Hifens nos nomes viram underscores, então arrow-right é exportado como arrow_right:
npx glyphfox build home search bell arrow-right --type package --name @acme/icons --weight 1.75 --out packages/icons
import { SvgXml } from 'react-native-svg'; import { home, arrow_right } from '@acme/icons'; export function Header() { return ( <> <SvgXml xml={home} width={24} height={24} color="#121014" /> <SvgXml xml={arrow_right} width={20} height={20} color="#121014" /> </> ); }
Metro e package exports
glyphfox/core e glyphfox/icons/NAME são subpath exports declarados no campo exports do pacote. O Metro lê esse campo por padrão a partir do React Native 0.79. Em versões anteriores, ative essa leitura na configuração do Metro; caso contrário, esses imports falham com um erro de "unable to resolve module":
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); module.exports = mergeConfig(getDefaultConfig(__dirname), { resolver: { unstable_enablePackageExports: true }, });
Sempre passe dados de ícone, e não nomes, para o motor. Os pacotes web carregam nomes desconhecidos com um import dinâmico da pasta de ícones, o que funciona em bundlers web, mas não é algo que o Metro acompanhe; com os dados em mãos, nada precisa ser carregado.
Estilos, modificadores e fusões
As ferramentas de composição do core também trabalham com dados. fuse combina de 2 a 10 ícones em um ícone novo que continua seguindo as regras dos ícones, e todas as opções abaixo são aplicadas quando a string é gerada:
import { fuse } from 'glyphfox/core'; import cloud from 'glyphfox/icons/cloud'; import lock from 'glyphfox/icons/lock'; import heart from 'glyphfox/icons/heart'; import { Glyph } from './Glyph'; const cloudLock = fuse([cloud, lock], { layout: 'overlap' }); export function Sync({ synced }: { synced: boolean }) { return ( <> <Glyph icon={cloudLock} size={28} /> <Glyph icon={cloud} modifier={synced ? 'check' : 'sync'} modifierCorner="top-right" /> <Glyph icon={heart} variant="solid" corners="sharp" /> </> ); }
Crie as fusões no nível do módulo ou memoize: fuse calcula a geometria do recorte, o que é barato uma vez, mas um desperdício a cada renderização.
Animando um morph
Não há CSS para fazer a transição, mas o próprio motor de morph é JavaScript puro. createMorph(from, to) prepara um morph uma única vez, usando automaticamente os pares ajustados à mão, como menu para x e play para pause, e frame(t) retorna os dados do ícone para qualquer progresso de 0 a 1. tween, do core, conduz o progresso com requestAnimationFrame:
import { useEffect, useRef, useState } from 'react'; import { AccessibilityInfo } from 'react-native'; import { SvgXml } from 'react-native-svg'; import { createMorph, renderToString, tween } from 'glyphfox/core'; import menu from 'glyphfox/icons/menu'; import x from 'glyphfox/icons/x'; const morph = createMorph(menu, x); export function MenuButtonIcon({ open, color = '#121014' }: { open: boolean; color?: string }) { const [t, setT] = useState(open ? 1 : 0); const current = useRef(t); current.current = t; useEffect(() => { let alive = true; let cancel = () => {}; AccessibilityInfo.isReduceMotionEnabled().then((reduce) => { if (alive) cancel = tween(current.current, open ? 1 : 0, { duration: reduce ? 0 : 280 }, setT); }); return () => { alive = false; cancel(); }; }, [open]); return <SvgXml xml={renderToString(morph.frame(t), { size: 24 })} width={24} height={24} color={color} />; }
Cada frame gera e faz o parse de uma string nova, o que não é problema para um único ícone durante algumas centenas de milissegundos. Evite rodar muitos morphs ao mesmo tempo e, para movimentos longos como um spinner, use uma transformação nativa em uma view que envolve o ícone.
Acessibilidade
Os atributos title, role e aria dentro da string SVG não chegam à árvore de acessibilidade nativa, então defina as props de acessibilidade no componente em volta do ícone. Um ícone dentro de um botão com rótulo não precisa de mais nada; um ícone isolado que carrega significado vira uma imagem com rótulo:
import { Pressable, View } from 'react-native'; import search from 'glyphfox/icons/search'; import cloud from 'glyphfox/icons/cloud'; import { Glyph } from './Glyph'; export function Status({ onSearch }: { onSearch: () => void }) { return ( <> <Pressable accessibilityRole="button" accessibilityLabel="Search" onPress={onSearch}> <Glyph icon={search} /> </Pressable> <View accessible accessibilityRole="image" accessibilityLabel="All changes synced"> <Glyph icon={cloud} modifier="check" /> </View> </> ); }
As regras gerais sobre quando um ícone precisa de rótulo estão no guia de acessibilidade.
O que fica de fora
| Recurso | Na web | No React Native |
|---|---|---|
Estados vivos (animate) | Keyframes em um elemento <style> dentro do SVG | O SvgXml ignora o elemento style. Anime uma view em volta do ícone com Animated ou Reanimated, por exemplo uma rotação para um spinner. |
Morph só com CSS (morphTo com um gatilho) | Uma transição CSS dos dados do path | Renderize frames com createMorph, como mostrado acima. |
mirrorRtl | Uma classe que espelha o ícone dentro de dir="rtl" | Passe flipX={I18nManager.isRTL} em ícones direcionais. |
title e decorative | Um elemento title, role="img" e aria-hidden | Props de acessibilidade no wrapper. |
| Tamanhos relativos | "1em", "2rem" | Somente números. |
Props
Estas opções de renderToString e toSvg alteram o próprio desenho e funcionam no React Native. Elas têm os mesmos nomes e o mesmo significado das props de todos os outros pacotes do glyphfox.
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
size | number, string | 24 | Largura e altura; números são pixels. |
color | string | currentColor | Cor principal. |
secondaryOpacity | number | 0.3 / 1 | Opacidade da camada duotone ou do preenchimento sólido. |
weight | number | 2 | Traço de 1 a 3 na grade de 24, com compensação óptica abaixo de 24 px. |
absoluteWeight | boolean | false | Mantém o traço com weight pixels em qualquer tamanho. |
corners | round, sharp | round | Junções e pontas redondas ou retas. |
variant | outline, duotone, solid | outline | Eixo de estilo; o sólido vem das formas fechadas. |
rotate | number | 0 | Rotação em graus. |
flipX | boolean | false | Espelha na horizontal. |
flipY | boolean | false | Espelha na vertical. |
modifier | plus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dot | nenhum | Selo com espaço de recorte automático. |
modifierCorner | top-left, top-right, bottom-left, bottom-right | bottom-right | Canto do selo. |
Armadilhas comuns
- Usar
glyphfox/react - Os componentes para web renderizam elementos do DOM, então o React Native para com um erro sobre a falta de view config para
svg. Renderize com oSvgXmlem vez disso. - O conjunto inteiro no bundle
import { toSvg } from 'glyphfox'leva todos os ícones para o app, porque o Metro mantém tudo. Importe os dados dos ícones um a um, ou gere um pacote de subconjunto.- Erro "Unable to resolve" em
glyphfox/core - Antes do React Native 0.79, os package exports vêm desativados no Metro. Ative
unstable_enablePackageExports. - Parse a cada renderização
- Uma chamada inline a
renderToStringem uma lista longa gera e faz o parse de uma string por linha e por renderização. Memoize a string, como faz o componente Glyph, ou use strings pré-geradas. - Nomes desconhecidos
toSvglança um erro para um nome que não existe. Valide os nomes que vêm de um servidor comiconNames, ou mapeie você mesmo esses nomes para os dados importados.
Perguntas frequentes
Funciona com Expo?
Sim. Instale o react-native-svg com npx expo install react-native-svg e use o mesmo componente Glyph. O Expo usa o Metro, então a observação sobre package exports vale aqui: SDKs baseados no React Native 0.79 ou mais recente não precisam de nenhuma mudança.
Como faço um spinner de carregamento?
Desenhe loader ou loader-circle com o componente Glyph e gire uma Animated.View em volta dele com uma animação timing em loop. O native driver mantém tudo fluido, e a string SVG é gerada uma única vez.