Ícones no Preact
O Preact roda os componentes React do glyphfox via preact/compat sem nenhuma mudança no código. Se o seu app não carrega o compat, o web component glyph-fox funciona no Preact puro.
Instalação
Instale o glyphfox junto com o Preact. O preact/compat faz parte do pacote preact, então não há uma segunda dependência para adicionar.
npm install glyphfox
Você tem duas formas de renderizar ícones, e elas compartilham o mesmo motor de renderização e as mesmas props:
- Os componentes de
glyphfox/react, executados pelo Preact via compat. A melhor opção quando o seu app já tem um alias para o React ou usa outros pacotes React. - O web component
<glyph-fox>, que não precisa de compat nenhum. A melhor opção quando você mantém o Preact enxuto e só precisa de ícones.
Como os componentes rodam no Preact
O ponto de entrada React importa exatamente seis coisas de react: createElement, forwardRef, useState, useEffect, useMemo e useRef. O compat implementa todas elas, então, assim que o bundler resolve react para preact/compat, os componentes renderizam com o próprio renderizador do Preact, na mesma árvore e com os mesmos hooks do resto do seu app.
Os atributos SVG são criados com os nomes do React, como strokeWidth e strokeLinecap, e o compat os converte de volta em stroke-width e stroke-linecap no elemento. As animações de estados vivos são um elemento <style> dentro do SVG, definido via dangerouslySetInnerHTML, que o Preact também suporta.
Vite
O preset oficial do Preact para o Vite já cria aliases de react, react-dom e do runtime JSX para o compat. Não é preciso mais nada:
import { defineConfig } from 'vite'; import preact from '@preact/preset-vite'; export default defineConfig({ plugins: [preact()], });
webpack e esbuild
No webpack, adicione os aliases em resolve.alias. O glyphfox só precisa de react, mas criar alias para o conjunto todo mantém qualquer outro pacote React do seu app na mesma instância do Preact:
module.exports = {
resolve: {
alias: {
react: 'preact/compat',
'react-dom/test-utils': 'preact/test-utils',
'react-dom': 'preact/compat',
'react/jsx-runtime': 'preact/jsx-runtime',
},
},
};O esbuild tem a mesma opção. Ative o code splitting ao mesmo tempo; caso contrário, o esbuild coloca inline todos os módulos de ícone que o carregador sob demanda consegue alcançar (o guia de tree shaking explica o motivo):
import * as esbuild from 'esbuild'; await esbuild.build({ entryPoints: ['src/main.jsx'], bundle: true, splitting: true, format: 'esm', outdir: 'dist', jsx: 'automatic', jsxImportSource: 'preact', alias: { react: 'preact/compat', 'react-dom': 'preact/compat' }, });
TypeScript
As declarações de glyphfox/react são escritas com base nos tipos do React (ForwardRefExoticComponent, SVGProps). Mapeie react para o compat em paths para que elas resolvam para os tipos do Preact. Se o verificador de tipos ainda apontar problemas dentro dos arquivos de declaração das dependências, mantenha skipLibCheck ligado, como faz a maioria dos templates de Preact:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact",
"skipLibCheck": true,
"paths": {
"react": ["./node_modules/preact/compat/"],
"react-dom": ["./node_modules/preact/compat/"]
}
}
}Use os componentes
Importe os ícones pelo nome e use os hooks do Preact em volta deles como de costume. Tudo o que está no guia de React vale aqui: Icon com a sintaxe de nomes, Fusion, MorphIcon, useMorph, modificadores e refs.
import { useState } from 'preact/hooks'; import { HeartIcon, MorphIcon, Fusion } from 'glyphfox/react'; export function Card() { const [liked, setLiked] = useState(false); return ( <article className="card"> <button type="button" onClick={() => setLiked(!liked)} aria-pressed={liked}> <HeartIcon animate="pop" trigger="state" active={liked} variant={liked ? 'solid' : 'outline'} /> Like </button> <MorphIcon name={liked ? 'check' : 'plus'} duration={250} /> <Fusion icons={['user', 'shield']} layout="badge" /> </article> ); }
As refs passam pelo forwardRef do compat, então ref num ícone aponta para o elemento <svg> renderizado, pronto para getBoundingClientRect() ou para a Web Animations API. Handlers de evento como onClick e atributos como data-* vão direto para o elemento, exatamente como no React.
Com signals, passe o valor, e não o signal. O Preact extrai o valor de signals ligados a atributos do DOM, mas um componente de ícone recebe o próprio objeto do signal e lê as props como valores simples, então ele não leria o valor certo nem atualizaria quando o signal mudasse:
import { signal } from '@preact/signals'; import { CloudIcon } from 'glyphfox/react'; const syncing = signal(false); export function Status() { return <CloudIcon modifier={syncing.value ? 'sync' : undefined} />; }
Sem compat: o web component
O custom element <glyph-fox> renderiza os mesmos ícones com as mesmas props, como atributos em kebab-case. Importe glyphfox/web uma vez para defini-lo com todos os ícones, ou defina o elemento você mesmo e registre só os ícones que usa:
import { defineGlyphFox } from 'glyphfox/web/element'; import { registerIcons } from 'glyphfox/core'; import { iconHome, iconSearch, iconMenu, iconX } from 'glyphfox'; registerIcons([iconHome, iconSearch, iconMenu, iconX]); defineGlyphFox();
import { useState } from 'preact/hooks'; import './icons.js'; export function Header() { const [open, setOpen] = useState(false); return ( <header> <glyph-fox name="home" size="20"></glyph-fox> <glyph-fox name="search" weight="1.5" title="Search"></glyph-fox> <button type="button" onClick={() => setOpen(!open)} aria-expanded={open} aria-label="Menu"> <glyph-fox name={open ? 'x' : 'menu'} morph></glyph-fox> </button> </header> ); }
O Preact define props desconhecidas em custom elements como atributos, que é o que o elemento observa, então valores dinâmicos como name={open ? 'x' : 'menu'} funcionam e o atributo morph anima cada mudança. Em TypeScript, adicione glyph-fox ao JSX.IntrinsicElements do Preact num arquivo de declaração, do jeito que a documentação do Preact descreve para qualquer custom element.
Atributos, dimensionamento dentro do shadow DOM e renderização do elemento no servidor são explicados no guia de web components.
Qual caminho é mais leve
Os dois caminhos trazem o mesmo motor (renderização, eixos, modificadores, fusão e morph), então os ícones custam mais ou menos o mesmo em qualquer um deles. A diferença real é o compat: se o seu app já o carrega por causa de outros pacotes, os componentes não acrescentam nada novo; se não carrega, o web component dispensa o compat. Nos dois casos, importe só os ícones que você usa, como mostrado acima.
Props
Os componentes aceitam estas props. No <glyph-fox>, as mesmas props são atributos em kebab-case (secondary-color, flip-x, morph-to), além de name, layout, gap e morph.
| Prop | Tipo | Padrão | Descrição |
|---|---|---|---|
size | number, string | 24 | Largura e altura; números são pixels. |
color | string | currentColor | Cor principal. |
secondaryColor | string | color | Cor da camada duotone e do preenchimento sólido. |
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. |
mirrorRtl | boolean | false | Espelha em layouts da direita para a esquerda. |
animate | draw, pop, wiggle, spin, pulse, bounce, shake | nenhum | Animação de estado vivo. |
trigger | mount, hover, click, state | mount | Quando a animação ou o morph acontece. |
active | boolean | false | O estado para o gatilho state. |
duration | number (ms) | por animação | Duração da animação ou do morph. |
delay | number (ms) | 0 | Atraso da animação ou do morph. |
loop | boolean, number | false | Repete para sempre ou um número de vezes (spin e pulse repetem por padrão). |
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. |
morphTo | name, data | nenhum | Destino do morph; o gatilho decide quando aparece. |
title | string | nenhum | Nome acessível: title, role img e aria-label. |
decorative | boolean | true sem title | Esconde o ícone das tecnologias assistivas. |
Armadilhas comuns
- Alias ausente
- Sem o alias, o bundler resolve
reactpara o pacote React de verdade (ou não o encontra). Aí os ícones chamam hooks de outro renderizador, o que aparece como erros de invalid hook call ou ícones que nunca atualizam. Confira sereactresolve parapreact/compat. classem vez declassName- Os componentes mesclam
classNamecom as próprias classesglyphfox glyphfox-NAME. Uma propclassnão é mesclada e pode ser substituída por elas, então useclassNamenos componentes de ícone. - Duas cópias do Preact
- O compat precisa resolver para o mesmo
preactdo seu app. Uma cópia duplicada emnode_modulesquebra os hooks do mesmo jeito que um alias ausente; remova a duplicata pelo seu gerenciador de pacotes. - Signals como props
- Passe
signal.valuepara os componentes de ícone. No caso de componentes, o Preact não extrai o valor do objeto do signal. - Elementos vazios antes da definição
- O
<glyph-fox>só desenha depois quedefineGlyphFox()roda no navegador. Com HTML pré-renderizado, dê um tamanho ao elemento no CSS para o layout não se deslocar enquanto o script carrega.
Perguntas frequentes
Preciso do preact/compat para usar o glyphfox?
Só para os componentes de glyphfox/react. O elemento <glyph-fox> e o toSvg de glyphfox funcionam no Preact puro sem ele.
Os componentes renderizam com preact-render-to-string?
Sim. Via compat, eles renderizam no servidor como qualquer componente React, e o SVG fica no HTML. Ícones renderizados por nome com Icon precisam ter os dados registrados antes, como descrito no guia de renderização no servidor.