Frameworks

Í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.

terminal
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:

vite.config.js
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:

webpack.config.js
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):

build.mjs
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:

tsconfig.json
{
  "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.

Card.jsx
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:

Status.jsx
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:

icons.js
import { defineGlyphFox } from 'glyphfox/web/element';
import { registerIcons } from 'glyphfox/core';
import { iconHome, iconSearch, iconMenu, iconX } from 'glyphfox';

registerIcons([iconHome, iconSearch, iconMenu, iconX]);
defineGlyphFox();
Header.jsx
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.

PropTipoPadrãoDescrição
sizenumber, string24Largura e altura; números são pixels.
colorstringcurrentColorCor principal.
secondaryColorstringcolorCor da camada duotone e do preenchimento sólido.
secondaryOpacitynumber0.3 / 1Opacidade da camada duotone ou do preenchimento sólido.
weightnumber2Traço de 1 a 3 na grade de 24, com compensação óptica abaixo de 24 px.
absoluteWeightbooleanfalseMantém o traço com weight pixels em qualquer tamanho.
cornersround, sharproundJunções e pontas redondas ou retas.
variantoutline, duotone, solidoutlineEixo de estilo; o sólido vem das formas fechadas.
rotatenumber0Rotação em graus.
flipXbooleanfalseEspelha na horizontal.
flipYbooleanfalseEspelha na vertical.
mirrorRtlbooleanfalseEspelha em layouts da direita para a esquerda.
animatedraw, pop, wiggle, spin, pulse, bounce, shakenenhumAnimação de estado vivo.
triggermount, hover, click, statemountQuando a animação ou o morph acontece.
activebooleanfalseO estado para o gatilho state.
durationnumber (ms)por animaçãoDuração da animação ou do morph.
delaynumber (ms)0Atraso da animação ou do morph.
loopboolean, numberfalseRepete para sempre ou um número de vezes (spin e pulse repetem por padrão).
modifierplus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dotnenhumSelo com espaço de recorte automático.
modifierCornertop-left, top-right, bottom-left, bottom-rightbottom-rightCanto do selo.
morphToname, datanenhumDestino do morph; o gatilho decide quando aparece.
titlestringnenhumNome acessível: title, role img e aria-label.
decorativebooleantrue sem titleEsconde o ícone das tecnologias assistivas.

Armadilhas comuns

Alias ausente
Sem o alias, o bundler resolve react para 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 se react resolve para preact/compat.
class em vez de className
Os componentes mesclam className com as próprias classes glyphfox glyphfox-NAME. Uma prop class não é mesclada e pode ser substituída por elas, então use className nos componentes de ícone.
Duas cópias do Preact
O compat precisa resolver para o mesmo preact do seu app. Uma cópia duplicada em node_modules quebra os hooks do mesmo jeito que um alias ausente; remova a duplicata pelo seu gerenciador de pacotes.
Signals como props
Passe signal.value para 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 que defineGlyphFox() 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.

Ícones deste guia